<?php

// phpcs:disable WordPress.DB.SlowDBQuery
/**
 * Image Optimization Module
 *
 * Generates WebP and AVIF versions of images for better performance
 *
 * @package ProRank\SEO\Modules\Performance
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Performance;

use ProRank\SEO\Core\Compatibility\PerformanceCompatibilityRegistry;
use ProRank\SEO\Modules\BaseModule;
use ProRank\SEO\Core\RestApi\ImageOptimizationController;
use ProRank\SEO\Core\ImageOptimization\OptimizationEngine;
use ProRank\SEO\Core\LicenseManager;

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


/**
 * ImageOptimizationModule class
 */


class ImageOptimizationModule extends BaseModule {
    
    /**
     * Supported image formats
     *
     * @var array
     */
    private const SUPPORTED_FORMATS = ['jpg', 'jpeg', 'png'];

    /**
     * MIME types that can be optimized by this module.
     *
     * @var array<int, string>
     */
    private const SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/png'];

    /**
     * MIME types that are already modern image originals and do not need conversion.
     *
     * @var array<int, string>
     */
    private const MODERN_ORIGINAL_MIME_TYPES = ['image/webp', 'image/avif', 'image/jxl'];
    
    /**
     * WebP quality default
     *
     * @var int
     */
    private const DEFAULT_WEBP_QUALITY = 75;
    
    /**
     * AVIF quality default
     *
     * @var int
     */
    private const DEFAULT_AVIF_QUALITY = 65;

    /**
     * JPEG XL quality default
     *
     * @var int
     */
    private const DEFAULT_JXL_QUALITY = 75;

    /**
     * Additional responsive widths that help browsers choose a closer match
     * when builders emit overly coarse core image candidates.
     *
     * @var array<int, int>
     */
    private const RESPONSIVE_SUBSIZES = [480, 640, 720, 960];

    /**
     * Default number of times a failed attachment can be retried during bulk optimization.
     *
     * @var int
     */
    private const DEFAULT_BULK_RETRY_LIMIT = 3;

    /**
     * Number of early images to force eager loading for (to reduce above-fold CLS).
     *
     * @var int
     */
    private const SKIP_LAZY_IMAGES = 10;

    /**
     * Output-buffer image counter for early-loading heuristics.
     *
     * @var int
     */
    private int $buffer_image_counter = 0;

    /**
     * Attachment-image counter for request-level LCP prioritization.
     *
     * @var int
     */
    private int $attachment_image_counter = 0;

    /**
     * In-request cache for resolved image dimensions by source URL.
     *
     * @var array<string, array{0:int,1:int}>
     */
    private array $dimension_lookup_cache = [];

    /**
     * In-request cache for runtime variant generation attempts.
     *
     * @var array<string, bool>
     */
    private array $runtime_variant_cache = [];

    /**
     * Parsed request/image exclusion patterns.
     *
     * @var array<int, string>|null
     */
    private ?array $exclude_path_patterns = null;

    /**
     * Compression types
     *
     * @var array
     */
    private const COMPRESSION_TYPES = [
        'aggressive' => [
            'jpeg_quality' => 70,
            'webp_quality' => 65,
            'avif_quality' => 55,
            'jxl_quality' => 60,
        ],
        'lossy' => [
            'jpeg_quality' => 80,
            'webp_quality' => 75,
            'avif_quality' => 65,
            'jxl_quality' => 75,
        ],
        'glossy' => [
            'jpeg_quality' => 85,
            'webp_quality' => 82,
            'avif_quality' => 75,
            'jxl_quality' => 82,
        ],
        'lossless' => [
            'jpeg_quality' => 100,
            'webp_quality' => 100,
            'avif_quality' => 100,
            'jxl_quality' => 100,
        ],
    ];
    
    /**
     * Get cloud optimization providers
     *
     * @return array
     */
    private function getCloudProviders(): array {
        return [
            'local' => [
                'name' => 'Local Server',
                'endpoint' => null,
                'features' => ['webp', 'avif', 'jxl'],
            ],
        ];
    }

    /**
     * Resolve a verified JWT token for cloud image operations.
     *
     * @return string|null
     */
    private function get_cloud_auth_token(): ?string {
        return null;
    }

    /**
     * Build cloud auth headers from JWT token.
     *
     * @param string $auth_token
     * @return array
     */
    private function build_cloud_auth_headers(string $auth_token): array {
        $site_url = get_site_url();
        $site_domain = wp_parse_url($site_url, PHP_URL_HOST) ?: '';

        return [
            'X-Site-Url' => $site_url,
            'X-Site-Domain' => $site_domain,
            'Authorization' => 'Bearer ' . $auth_token,
        ];
    }
    
    /**
     * Constructor
     */
    public function __construct() {
        $this->slug = 'image_optimization';
        $this->name = 'Advanced Image Optimization';
        $this->description = 'Next-gen image optimization with WebP, AVIF, PDF support and AI-powered features';
        $this->feature_tier = 'free';
        $this->parent_slug = 'performance';
    }

    /**
     * Override settings lookup to prefer the dedicated option store.
     *
     * REST controllers persist settings into the `prorank_image_optimization_settings`
     * option. To keep compatibility with the SettingsManager, check that option first
     * and fall back to the base implementation when not present.
     *
     * @param string $key     Setting key.
     * @param mixed  $default Default value.
     * @return mixed
     */
    protected function get_setting(string $key, $default = null) {
        $option_settings = get_option('prorank_image_optimization_settings', []);

        if (is_array($option_settings) && array_key_exists($key, $option_settings)) {
            return $option_settings[$key];
        }

        return parent::get_setting($key, $default);
    }
    
    /**
     * Initialize module hooks
     *
     * @return void
     */
    public function init_hooks(): void {
        // Always register bulk/background optimization handlers (triggered by REST API)
        // These must be outside should_run() check so bulk optimization works
        add_action('prorank_bulk_optimize_images', [$this, 'process_bulk_optimization']);
        add_action('prorank_optimize_image', [$this, 'process_single_image_optimization']);
        add_action('prorank_bulk_optimization_continue', [$this, 'handle_bulk_optimization_continue'], 10, 2);

        // Check if module should run for other hooks
        if (!$this->should_run()) {
            return;
        }

        $scaled_sizes_enabled = $this->should_enable_scaled_image_sizes();

        if ($scaled_sizes_enabled) {
            add_action('init', [$this, 'register_image_sizes']);
        }

        $frontend_delivery_context = $this->is_frontend_optimization_context();
        $full_optimization_runtime = $this->has_full_optimization_runtime();
        $intrinsic_dimensions_enabled = $this->should_enable_intrinsic_dimensions();

        // Backfill intrinsic dimensions/fetchpriority for attachment-based images
        // (covers builders/widgets that bypass the_content filters).
        if ($frontend_delivery_context && $intrinsic_dimensions_enabled) {
            add_filter('wp_get_attachment_image_attributes', [$this, 'optimize_attachment_image_attributes'], 15, 3);
            add_filter('the_content', [$this, 'ensure_content_image_dimensions'], 98);
            add_filter('the_excerpt', [$this, 'ensure_content_image_dimensions'], 98);
            add_filter('widget_text', [$this, 'ensure_content_image_dimensions'], 98);
            add_filter('widget_custom_html', [$this, 'ensure_content_image_dimensions'], 98);
            add_action('wp_footer', [$this, 'print_intrinsic_dimension_restorer'], 100);
        }

        if ($frontend_delivery_context && !$full_optimization_runtime && ($intrinsic_dimensions_enabled || $scaled_sizes_enabled)) {
            add_action('template_redirect', [$this, 'start_dimension_buffer'], 1);
        }

        if (!$full_optimization_runtime) {
            return;
        }
        
        // Hook into image upload for auto-optimization
        if ($this->get_setting('optimize_on_upload', true)) {
            add_filter('wp_generate_attachment_metadata', [$this, 'generate_optimized_images'], 10, 2);
        }
        
        // Hook to delete optimized images when original is deleted
        add_action('delete_attachment', [$this, 'delete_optimized_images']);
        
        // Hook for PNG to JPEG conversion
        if ($this->get_setting('png_to_jpeg', false)) {
            add_filter('wp_generate_attachment_metadata', [$this, 'convert_png_to_jpeg'], 9, 2);
        }
        
        // Hook for CMYK to RGB conversion
        if ($this->get_setting('cmyk_to_rgb', true)) {
            add_filter('wp_generate_attachment_metadata', [$this, 'convert_cmyk_to_rgb'], 8, 2);
        }
        
        // Hook for EXIF data management
        if ($this->get_setting('remove_exif', true)) {
            add_filter('wp_generate_attachment_metadata', [$this, 'manage_exif_data'], 7, 2);
        }
        
        // Hook for PDF optimization
        if ($this->get_setting('optimize_pdf', false)) {
            add_filter('wp_generate_attachment_metadata', [$this, 'optimize_pdf'], 10, 2);
        }
        
        // Hook for retina image generation
        if ($this->get_setting('generate_retina', false)) {
            add_filter('wp_generate_attachment_metadata', [$this, 'generate_retina_images'], 11, 2);
        }
        
        // Hook for AI training protection
        if ($this->get_setting('no_ai_training', false)) {
            add_action('add_attachment', [$this, 'add_ai_training_protection']);
            if ($frontend_delivery_context) {
                add_filter('wp_get_attachment_image_attributes', [$this, 'add_noai_attributes'], 10, 3);
            }
        }
        
        // Hook for processing failed optimization queue
        add_action('prorank_retry_failed_optimizations', [$this, 'process_failed_optimization_queue']);
        
        // Get delivery method
        $delivery_method = $this->get_setting('delivery_method', 'picture_hooks');

        // 'picture' should use output buffering to catch ALL images (including Elementor)
        // 'picture_hooks' only works for images in the_content, which Elementor bypasses
        if ($delivery_method === 'picture') {
            $delivery_method = 'picture_global';
        }

        if ($frontend_delivery_context && $delivery_method === 'picture_hooks') {
            // Filter to convert img to picture element (only for hooks mode)
            add_filter('wp_get_attachment_image', [$this, 'convert_to_picture_element'], 10, 5);

            // Filter for post content images
            add_filter('the_content', [$this, 'convert_content_images'], 99);

            // Filter for excerpt
            add_filter('the_excerpt', [$this, 'convert_content_images'], 99);

            // Filter for widget text
            add_filter('widget_text', [$this, 'convert_content_images'], 99);

            // Filter for custom HTML widget
            add_filter('widget_custom_html', [$this, 'convert_content_images'], 99);
        } elseif ($frontend_delivery_context && $delivery_method === 'picture_global') {
            // Process entire output buffer - this catches ALL images including Elementor
            // Don't add wp_get_attachment_image filter to avoid double-wrapping
            add_action('template_redirect', [$this, 'start_output_buffer'], 1);
        } elseif ($delivery_method === 'htaccess') {
            // Generate .htaccess rules
            add_action('init', [$this, 'setup_htaccess_rules']);
        } elseif ($frontend_delivery_context && $delivery_method === 'accept_header') {
            // Server-side Accept header detection - replaces URLs directly
            // Works for CSS backgrounds and all images, not just img tags
            add_action('template_redirect', [$this, 'start_accept_header_buffer'], 1);
        }

        // Client Hints support (response headers for DPR/Width/Viewport)
        if ($frontend_delivery_context && $this->get_setting('client_hints_enabled', false)) {
            add_action('send_headers', [$this, 'send_client_hints_headers']);
        }
        
        // AJAX handler for bulk conversion
        add_action('wp_ajax_prorank_convert_images', [$this, 'handle_bulk_conversion']);

        // Note: prorank_optimize_image action is registered above should_run() check
        // to ensure Action Scheduler actions work even when module conditionals fail

        // Add rewrite rules for serving optimized images
        add_action('init', [$this, 'add_rewrite_rules']);
        
        // Schedule background optimization cron if enabled
        if ($this->get_setting('background_mode', false)) {
            add_action('init', [$this, 'schedule_background_optimization']);
            add_action('prorank_background_image_optimization', [$this, 'run_background_optimization']);
        }
        
        // Custom media folders admin menu
        if ($this->get_setting('custom_media_folders', false)) {
            add_action('admin_menu', [$this, 'add_custom_media_menu']);
        }
        
        // CDN integration
        $cdn_delivery_enabled = !empty($this->get_setting('cdn_url', ''))
            && (
                $this->get_setting('cdn_enabled', false)
                || $delivery_method === 'cdn'
            );

        if ($frontend_delivery_context && $cdn_delivery_enabled) {
            add_filter('wp_get_attachment_url', [$this, 'rewrite_cdn_url'], 10, 2);
            add_filter('wp_calculate_image_srcset', [$this, 'rewrite_srcset_cdn'], 10, 5);
            add_filter('the_content', [$this, 'rewrite_content_cdn_urls'], 100);
        }
    }

    /**
     * Restrict HTML/image delivery mutations to real frontend page renders.
     *
     * Builder/admin/REST/AJAX contexts can persist transformed markup back into
     * stored content, which must never happen.
     *
     * @return bool
     */
    private function is_frontend_optimization_context(): bool {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return false;
        }

        if ((defined('REST_REQUEST') && REST_REQUEST) || (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST)) {
            return false;
        }

        if (defined('WP_CLI') && WP_CLI) {
            return false;
        }

        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return false;
        }

        return true;
    }

    /**
     * Optimize attachment image attributes at render time.
     *
     * Ensures width/height exist for CLS stability and adds a conservative
     * LCP hint (fetchpriority=high + eager) on the first eligible content image.
     *
     * @param array        $attr       Image attributes.
     * @param \WP_Post|int $attachment Attachment object or ID.
     * @param string|array $size       Requested size.
     * @return array
     */
    public function optimize_attachment_image_attributes(array $attr, $attachment, $size): array {
        if (!$this->is_frontend_optimization_context()) {
            return $attr;
        }

        $width = isset($attr['width']) ? (int) $attr['width'] : 0;
        $height = isset($attr['height']) ? (int) $attr['height'] : 0;

        $attachment_id = 0;
        if ($attachment instanceof \WP_Post) {
            $attachment_id = (int) $attachment->ID;
        } elseif (is_numeric($attachment)) {
            $attachment_id = (int) $attachment;
        }

        if (($width <= 0 || $height <= 0) && $attachment_id > 0) {
            $image_data = wp_get_attachment_image_src($attachment_id, $size ?: 'full');
            if (is_array($image_data)) {
                $resolved_width = isset($image_data[1]) ? (int) $image_data[1] : 0;
                $resolved_height = isset($image_data[2]) ? (int) $image_data[2] : 0;
                if ($resolved_width > 0 && $resolved_height > 0) {
                    $attr['width'] = $resolved_width;
                    $attr['height'] = $resolved_height;
                }
            }
        }

        $attr = $this->append_intrinsic_dimension_data_attrs($attr);

        if (!$this->is_lcp_fetchpriority_enabled()) {
            return $attr;
        }

        $class_value = strtolower((string) ($attr['class'] ?? ''));
        $alt_value = strtolower((string) ($attr['alt'] ?? ''));
        $src_value = strtolower((string) ($attr['src'] ?? ''));
        $is_branding_asset = false;
        foreach (['logo', 'site-icon', 'favicon', 'avatar'] as $needle) {
            if (
                ($class_value !== '' && strpos($class_value, $needle) !== false)
                || ($alt_value !== '' && strpos($alt_value, $needle) !== false)
                || ($src_value !== '' && strpos($src_value, $needle) !== false)
            ) {
                $is_branding_asset = true;
                break;
            }
        }

        if ($is_branding_asset) {
            return $attr;
        }

        if ($this->should_defer_fetchpriority_assignment_to_lcp_modules()) {
            return $attr;
        }

        if ($this->attachment_image_counter === 0) {
            if (empty($attr['fetchpriority'])) {
                $attr['fetchpriority'] = 'high';
            }
            if (($attr['loading'] ?? '') === 'lazy') {
                $attr['loading'] = 'eager';
            }
        }
        $this->attachment_image_counter++;

        return $attr;
    }

    /**
     * Check if fetchpriority hints are enabled through asset optimization settings.
     *
     * @return bool
     */
    private function is_lcp_fetchpriority_enabled(): bool {
        $settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($settings)) {
            return false;
        }

        foreach (['lcp_fetchpriority_enabled', 'lcp_optimizer_enabled', 'lcp_optimize_first_image'] as $key) {
            $value = $settings[$key] ?? false;
            if ($value === true || $value === 1 || $value === '1' || $value === 'true' || $value === 'yes' || $value === 'on') {
                return true;
            }
        }

        return false;
    }

    /**
     * Let dedicated LCP modules own priority selection when a known target exists.
     *
     * @return bool
     */
    private function should_defer_fetchpriority_assignment_to_lcp_modules(): bool {
        if (!empty($GLOBALS['prorank_lcp_image_url']) && is_string($GLOBALS['prorank_lcp_image_url'])) {
            return true;
        }

        return $this->get_known_lcp_image_url_for_current_request() !== '';
    }

    /**
     * Resolve the current request's known LCP asset from preload, optimizer, or beacon data.
     *
     * @return string
     */
    private function get_known_lcp_image_url_for_current_request(): string {
        $preloaded = $GLOBALS['prorank_preloaded_urls'] ?? [];
        if (is_array($preloaded)) {
            foreach ($preloaded as $entry) {
                if (!is_string($entry) || trim($entry) === '') {
                    continue;
                }

                $candidate = trim((string) explode('|', $entry, 2)[0]);
                if (preg_match('/\.(avif|webp|jpe?g|png|gif|svg)(\?|$)/i', $candidate) === 1) {
                    return $candidate;
                }
            }
        }

        $optimizer_data = get_option('prorank_lcp_optimizer_data', []);
        if (is_array($optimizer_data)) {
            $page_key = $this->get_current_lcp_page_template_key();
            $entry = $page_key !== '' ? ($optimizer_data[$page_key] ?? null) : null;
            if (is_array($entry) && !empty($entry['url']) && is_string($entry['url'])) {
                return esc_url_raw($entry['url']);
            }
        }

        $beacon_data = get_option('prorank_lcp_beacon_data', []);
        if (is_array($beacon_data)) {
            $pattern = $this->get_current_lcp_url_pattern();
            $entry = $pattern !== '' ? ($beacon_data[$pattern] ?? null) : null;
            if (is_array($entry)) {
                $background = isset($entry['background_image']) && is_string($entry['background_image']) ? esc_url_raw($entry['background_image']) : '';
                if ($background !== '') {
                    return $background;
                }

                $src = isset($entry['src']) && is_string($entry['src']) ? esc_url_raw($entry['src']) : '';
                if ($src !== '') {
                    return $src;
                }
            }
        }

        return '';
    }

    /**
     * Match the LCP optimizer page-key scheme for the current request.
     *
     * @return string
     */
    private function get_current_lcp_page_template_key(): string {
        if (is_front_page()) {
            return 'front_page';
        }
        if (is_home()) {
            return 'blog_home';
        }
        if (is_singular('post')) {
            return 'single_post';
        }
        if (is_singular('page')) {
            $template = get_page_template_slug();
            return 'page_' . ($template ?: 'default');
        }
        if (is_singular()) {
            return 'single_' . get_post_type();
        }
        if (is_archive()) {
            if (is_category()) {
                return 'archive_category';
            }
            if (is_tag()) {
                return 'archive_tag';
            }
            if (is_author()) {
                return 'archive_author';
            }
            return 'archive_' . (get_post_type() ?: 'post');
        }
        if (is_search()) {
            return 'search';
        }
        if (is_404()) {
            return '404';
        }

        return 'other';
    }

    /**
     * Match the beacon module URL-pattern scheme for the current request.
     *
     * @return string
     */
    private function get_current_lcp_url_pattern(): string {
        $url = $this->get_current_request_url();
        if ($url === '') {
            return '';
        }

        $parsed = wp_parse_url($url);
        $path = $parsed['path'] ?? '/';
        $path = trim((string) $path, '/');
        if ($path === '') {
            return 'home';
        }

        $path = preg_replace('/\/\d+\//', '/*/', $path);
        $path = preg_replace('/\/\d+$/', '/*', $path);
        $parts = explode('/', $path);
        $parts = array_slice($parts, 0, 3);

        return implode('/', $parts);
    }

    /**
     * Build the current request URL for beacon-style lookup.
     *
     * @return string
     */
    private function get_current_request_url(): string {
        $protocol = is_ssl() ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? '';
        $uri = $_SERVER['REQUEST_URI'] ?? '';

        if (!is_string($host) || !is_string($uri) || $host === '') {
            return '';
        }

        return $protocol . '://' . $host . $uri;
    }
    
    /**
     * Check if module should run
     *
     * @return bool
     */
    private function should_run(): bool {
        // Check if module is enabled
        if (!$this->is_enabled()) {
            return false;
        }

        return $this->has_full_optimization_runtime()
            || $this->should_enable_intrinsic_dimensions()
            || $this->should_enable_scaled_image_sizes();
    }

    /**
     * Whether intrinsic dimensions support should run on the frontend.
     *
     * This can be enabled explicitly as a standalone low-risk feature, or
     * implicitly when the full image optimization runtime is active.
     *
     * @return bool
     */
    private function should_enable_intrinsic_dimensions(): bool {
        return $this->get_setting('image_dimensions_enabled', false)
            || $this->has_full_optimization_runtime();
    }

    /**
     * Whether responsive sub-sizes should be registered even when modern
     * format delivery is disabled.
     *
     * @return bool
     */
    private function should_enable_scaled_image_sizes(): bool {
        return $this->get_setting('serve_scaled_images', true)
            || $this->get_setting('enable_responsive_images', true);
    }

    /**
     * Determine whether the full image optimization runtime should boot.
     *
     * @return bool
     */
    private function has_full_optimization_runtime(): bool {
        $cdn_requested = !empty($this->get_setting('cdn_url', ''))
            && (
                $this->get_setting('cdn_enabled', false)
                || $this->get_setting('delivery_method', 'picture') === 'cdn'
            );
        
        return $this->get_setting('webp_enabled', false) ||
               $this->get_setting('avif_enabled', false) ||
               $this->get_setting('jxl_enabled', false) ||
               $this->get_setting('optimize_on_upload', false) ||
               $this->get_setting('background_mode', false) ||
               $cdn_requested;
    }

    /**
     * Backfill intrinsic dimensions in generic content/html strings.
     *
     * This mirrors the low-risk image-dimension approach used by competitors:
     * mutate only local <img> tags and leave delivery/format rewriting off.
     *
     * @param string $content
     * @return string
     */
    public function ensure_content_image_dimensions(string $content): string {
        if (!$this->is_frontend_optimization_context() || stripos($content, '<img') === false) {
            return $content;
        }

        return preg_replace_callback(
            '/<img\b[^>]*>/i',
            fn(array $matches): string => $this->ensure_intrinsic_dimensions($matches[0]),
            $content
        ) ?: $content;
    }

    /**
     * Start a lightweight output buffer that only backfills intrinsic dimensions.
     *
     * @return void
     */
    public function start_dimension_buffer(): void {
        if (!$this->is_frontend_optimization_context()) {
            return;
        }

        ob_start([$this, 'process_dimension_buffer']);
    }

    /**
     * Process raw HTML and backfill intrinsic dimensions on local images.
     *
     * @param string $buffer
     * @return string
     */
    public function process_dimension_buffer(string $buffer): string {
        if (stripos($buffer, '<img') === false) {
            return $buffer;
        }

        return $this->refresh_buffer_inline_images($buffer);
    }

    /**
     * Refresh inline image markup from live attachment metadata.
     *
     * This keeps builder-saved HTML in sync when image sub-sizes or srcset
     * candidates change after the original markup was stored.
     *
     * @param string $img_html
     * @return string
     */
    private function refresh_inline_image_markup(string $img_html, string $sizes_override = ''): string {
        $img_html = $this->ensure_intrinsic_dimensions($img_html);

        if (!$this->should_enable_scaled_image_sizes()) {
            return $img_html;
        }

        $attributes = $this->extract_img_attributes($img_html);
        if ($attributes === '') {
            return $img_html;
        }

        $attachment_id = $this->extract_attachment_id_from_img_attributes($attributes);
        if ($attachment_id <= 0) {
            return $img_html;
        }

        $size_hint = $this->extract_size_hint_from_img_attributes($attributes);
        $srcset = wp_get_attachment_image_srcset($attachment_id, $size_hint);
        if (is_string($srcset) && $srcset !== '') {
            $srcset = $this->prune_oversized_srcset_candidates($srcset, $img_html);
            $img_html = $this->replace_or_insert_attribute($img_html, 'srcset', $srcset);
        }

        $sizes = $sizes_override !== '' ? $sizes_override : wp_get_attachment_image_sizes($attachment_id, $size_hint);
        if (is_string($sizes) && $sizes !== '') {
            $img_html = $this->replace_or_insert_attribute($img_html, 'sizes', $sizes);
        }

        return $img_html;
    }

    /**
     * Extract attachment ID from an image class list.
     *
     * @param string $attributes
     * @return int
     */
    private function extract_attachment_id_from_img_attributes(string $attributes): int {
        if (!preg_match('/\bwp-image-(\d+)\b/i', $attributes, $matches)) {
            return 0;
        }

        return (int) ($matches[1] ?? 0);
    }

    /**
     * Infer the requested size name from image classes.
     *
     * @param string $attributes
     * @return string
     */
    private function extract_size_hint_from_img_attributes(string $attributes): string {
        if (!preg_match('/\bsize-([a-z0-9_-]+)\b/i', $attributes, $matches)) {
            return 'full';
        }

        $size = strtolower((string) ($matches[1] ?? 'full'));
        return $size !== '' ? $size : 'full';
    }

    /**
     * Replace or insert an HTML attribute on an img tag.
     *
     * @param string $img_html
     * @param string $attribute
     * @param string $value
     * @return string
     */
    private function replace_or_insert_attribute(string $img_html, string $attribute, string $value): string {
        $escaped = esc_attr($value);
        $pattern = '/\b' . preg_quote($attribute, '/') . '\s*=\s*(["\']).*?\1/i';

        if (preg_match($pattern, $img_html)) {
            return (string) preg_replace($pattern, $attribute . '="' . $escaped . '"', $img_html, 1);
        }

        return preg_replace('/<img\b/i', '<img ' . $attribute . '="' . $escaped . '"', $img_html, 1) ?: $img_html;
    }

    /**
     * Drop oversized srcset candidates for explicitly-sized lazy images.
     *
     * Browsers on high-DPR mobile screens can choose a full-size candidate even
     * when the markup requested a small rendered image. That keeps WebP delivery
     * working but still triggers PageSpeed "larger than displayed dimensions"
     * diagnostics. For lazy images with a concrete width attribute, cap srcset
     * candidates to the requested rendered width plus conservative tolerance.
     *
     * @param string $srcset   Candidate list.
     * @param string $img_html Current img tag.
     * @return string
     */
    private function prune_oversized_srcset_candidates(string $srcset, string $img_html): string {
        if (!$this->get_setting('serve_scaled_images', true) && !$this->get_setting('enable_responsive_images', true)) {
            return $srcset;
        }

        $attributes = $this->extract_img_attributes($img_html);
        if (strtolower($this->extract_attribute_value($attributes, 'loading')) !== 'lazy') {
            return $srcset;
        }

        if (strtolower($this->extract_attribute_value($attributes, 'fetchpriority')) === 'high') {
            return $srcset;
        }

        [$render_width] = $this->extract_dimensions_from_img_tag($img_html);
        if ($render_width <= 0) {
            return $srcset;
        }

        $max_candidate_width = (int) apply_filters(
            'prorank_image_srcset_prune_max_width',
            max($render_width + 64, (int) ceil($render_width * 1.35)),
            $render_width,
            $img_html,
            $srcset
        );
        if ($max_candidate_width < $render_width) {
            return $srcset;
        }

        $parts = array_filter(array_map('trim', explode(',', $srcset)));
        if (count($parts) < 2) {
            return $srcset;
        }

        $kept = [];
        $dropped = 0;
        foreach ($parts as $part) {
            if (!preg_match('/^(\S+)\s+(\d+)w$/', $part, $matches)) {
                $kept[] = $part;
                continue;
            }

            $candidate_width = (int) ($matches[2] ?? 0);
            if ($candidate_width <= 0 || $candidate_width <= $max_candidate_width) {
                $kept[] = $part;
                continue;
            }

            $dropped++;
        }

        if ($dropped === 0 || empty($kept)) {
            return $srcset;
        }

        return implode(', ', $kept);
    }

    /**
     * Refresh all inline img tags in an HTML buffer.
     *
     * @param string $buffer
     * @return string
     */
    private function refresh_buffer_inline_images(string $buffer): string {
        if (!preg_match_all('/<img\b[^>]*>/i', $buffer, $matches, PREG_OFFSET_CAPTURE)) {
            return $buffer;
        }

        $rebuilt = '';
        $cursor = 0;

        foreach ($matches[0] as $match) {
            $img_html = (string) ($match[0] ?? '');
            $offset = (int) ($match[1] ?? 0);

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

            $rebuilt .= substr($buffer, $cursor, $offset - $cursor);

            // Premium Addons/Elementor carousel wrappers can sit far ahead of
            // later item images once template markup is expanded. Keep the
            // lookback generous so all items inherit the same responsive sizes.
            $context_start = max(0, $offset - 20000);
            $context = substr($buffer, $context_start, $offset - $context_start);
            $sizes_override = $this->detect_known_builder_sizes_from_context($context);

            $rebuilt .= $this->refresh_inline_image_markup($img_html, $sizes_override);
            $cursor = $offset + strlen($img_html);
        }

        $rebuilt .= substr($buffer, $cursor);
        return $rebuilt;
    }

    /**
     * Detect a known builder/container sizes formula from the surrounding HTML.
     *
     * @param string $context
     * @return string
     */
    private function detect_known_builder_sizes_from_context(string $context): string {
        if ($context === '' || stripos($context, 'premium-carousel-wrapper') === false) {
            return '';
        }

        if (!preg_match_all('/premium-carousel-wrapper[^>]+data-settings=(["\'])(.*?)\1/is', $context, $matches, PREG_SET_ORDER)) {
            return '';
        }

        $last = end($matches);
        if (!is_array($last) || empty($last[2])) {
            return '';
        }

        $settings_json = html_entity_decode((string) $last[2], ENT_QUOTES, 'UTF-8');
        $settings = json_decode($settings_json, true);
        if (!is_array($settings)) {
            return '';
        }

        $wrapper_pos = strripos($context, 'premium-carousel-wrapper');
        $boxed_pos = $wrapper_pos !== false ? strripos(substr($context, 0, $wrapper_pos), 'elementor-section-boxed') : false;
        $is_boxed_layout = $wrapper_pos !== false && $boxed_pos !== false && ($wrapper_pos - $boxed_pos) < 5000;

        return $this->build_premium_carousel_sizes_string($settings, $is_boxed_layout);
    }

    /**
     * Build a responsive sizes string for Premium Addons carousel images.
     *
     * @param array<string,mixed> $settings
     * @return string
     */
    private function build_premium_carousel_sizes_string(array $settings, bool $is_boxed_layout = false): string {
        $slides_mobile = max(1, (int) ($settings['slidesMob'] ?? $settings['slidesToShow'] ?? 1));
        $slides_tablet = max(1, (int) ($settings['slidesTab'] ?? $settings['slidesDesk'] ?? $settings['slidesToShow'] ?? $slides_mobile));
        $slides_desktop = max(1, (int) ($settings['slidesDesk'] ?? $settings['slidesToShow'] ?? $slides_tablet));

        $mobile_break = max(480, (int) ($settings['mobileBreak'] ?? 768));
        $tablet_break = max($mobile_break + 1, (int) ($settings['tabletBreak'] ?? 1025));
        $desktop_size = $this->format_viewport_fraction($slides_desktop);

        if ($is_boxed_layout && $slides_desktop > 1) {
            $desktop_size = $this->estimate_boxed_carousel_slide_size($slides_desktop);
        }

        return sprintf(
            '(max-width: %1$dpx) %2$s, (max-width: %3$dpx) %4$s, %5$s',
            $mobile_break - 1,
            $this->format_viewport_fraction($slides_mobile),
            $tablet_break - 1,
            $this->format_viewport_fraction($slides_tablet),
            $desktop_size
        );
    }

    /**
     * Estimate a desktop slide width for carousels inside boxed Elementor sections.
     *
     * @param int $slides
     * @return string
     */
    private function estimate_boxed_carousel_slide_size(int $slides): string {
        $slides = max(1, $slides);
        $container_width = (int) get_option('elementor_container_width', 1140);
        if ($container_width <= 0) {
            $container_width = 1140;
        }

        $available_width = max(320, $container_width - 20);
        $slide_width = (int) floor($available_width / $slides);

        return max(160, $slide_width) . 'px';
    }

    /**
     * Convert a slide count into a viewport-width size token.
     *
     * @param int $slides
     * @return string
     */
    private function format_viewport_fraction(int $slides): string {
        $slides = max(1, $slides);
        if ($slides === 1) {
            return '100vw';
        }

        $vw = 100 / $slides;
        $formatted = rtrim(rtrim(number_format($vw, 2, '.', ''), '0'), '.');
        return $formatted . 'vw';
    }

    /**
     * Print a tiny runtime restorer for lazy widgets that strip width/height.
     *
     * @return void
     */
    public function print_intrinsic_dimension_restorer(): void {
        if (!$this->is_frontend_optimization_context()) {
            return;
        }

        echo "<script>(function(){var dimensionMap=new Map();var remember=function(img){if(!(img instanceof HTMLImageElement))return;var w=img.getAttribute('width')||img.dataset.prorankWidth;var h=img.getAttribute('height')||img.dataset.prorankHeight;if(!w||!h)return;[img.getAttribute('src'),img.getAttribute('data-src'),img.currentSrc].forEach(function(key){if(key)dimensionMap.set(key,{w:w,h:h});});};var resolve=function(img){remember(img);var w=img.getAttribute('width')||img.dataset.prorankWidth;var h=img.getAttribute('height')||img.dataset.prorankHeight;if(w&&h)return{w:w,h:h};var key=img.currentSrc||img.getAttribute('src')||img.getAttribute('data-src');return key?dimensionMap.get(key)||null:null;};var apply=function(img){if(!(img instanceof HTMLImageElement))return;var dims=resolve(img);if(!dims)return;if(!img.hasAttribute('width'))img.setAttribute('width',dims.w);if(!img.hasAttribute('height'))img.setAttribute('height',dims.h);if(!img.style.aspectRatio)img.style.aspectRatio=dims.w+' / '+dims.h;};var scan=function(root){(root||document).querySelectorAll('img').forEach(function(img){remember(img);apply(img);});};if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',function(){scan(document);},{once:true});}else{scan(document);}var observer=new MutationObserver(function(mutations){mutations.forEach(function(mutation){if(mutation.type==='attributes'&&mutation.target&&mutation.target.tagName==='IMG'){apply(mutation.target);}mutation.addedNodes.forEach(function(node){if(!node||node.nodeType!==1)return;if(node.tagName==='IMG'){apply(node);}if(node.querySelectorAll){node.querySelectorAll('img').forEach(function(img){apply(img);});}});});});observer.observe(document.documentElement,{subtree:true,childList:true,attributes:true,attributeFilter:['src','data-src','width','height','class']});})();</script>";
    }

    /**
     * Get normalized exclusion patterns from settings.
     *
     * @return array<int, string>
     */
    private function get_exclude_path_patterns(): array {
        if ($this->exclude_path_patterns !== null) {
            return $this->exclude_path_patterns;
        }

        $raw = (string) $this->get_setting('exclude_paths', '');
        if ($raw === '') {
            $this->exclude_path_patterns = [];
            return $this->exclude_path_patterns;
        }

        $this->exclude_path_patterns = array_values(
            array_filter(
                array_map(
                    'trim',
                    preg_split('/\r\n|\r|\n/', $raw) ?: []
                ),
                static fn(string $pattern): bool => $pattern !== ''
            )
        );

        return $this->exclude_path_patterns;
    }

    /**
     * Check whether a URL/path matches a configured exclusion pattern.
     *
     * @param string $value URL or path.
     * @return bool
     */
    private function matches_exclude_path(string $value): bool {
        $value = trim($value);
        if ($value === '') {
            return false;
        }

        $candidates = [$value];
        $parsed_path = (string) wp_parse_url($value, PHP_URL_PATH);
        if ($parsed_path !== '') {
            $candidates[] = $parsed_path;
        }

        foreach ($this->get_exclude_path_patterns() as $pattern) {
            foreach ($candidates as $candidate) {
                if ($candidate !== '' && stripos($candidate, $pattern) !== false) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check whether the current frontend request is excluded.
     *
     * @return bool
     */
    private function is_current_request_excluded(): bool {
        $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : '';
        if ($request_uri === '') {
            return false;
        }

        return $this->matches_exclude_path($request_uri) || $this->matches_exclude_path(home_url($request_uri));
    }

    /**
     * Generate missing runtime variants for a local uploads URL when needed.
     *
     * This covers site-rendered images that are not registered media attachments.
     *
     * @param string $source_url Source uploads URL.
     * @return void
     */
    private function ensure_runtime_optimized_variants(string $source_url): void {
        $normalized_url = preg_replace('/[#?].*$/', '', trim($source_url)) ?: trim($source_url);
        if ($normalized_url === '' || isset($this->runtime_variant_cache[$normalized_url])) {
            return;
        }

        $this->runtime_variant_cache[$normalized_url] = true;

        if ($this->matches_exclude_path($normalized_url)) {
            return;
        }

        $source_path = $this->map_upload_url_to_path($normalized_url);
        if ($source_path === '' || !file_exists($source_path)) {
            return;
        }

        $extension = strtolower(pathinfo($source_path, PATHINFO_EXTENSION));
        if (!in_array($extension, self::SUPPORTED_FORMATS, true)) {
            return;
        }

        if ($this->get_setting('webp_enabled', false) && !$this->optimized_file_exists($this->get_optimized_url($normalized_url, 'webp'))) {
            $this->generate_webp($source_path, 0);
        }

        if ($this->get_setting('avif_enabled', false) && $this->can_generate_avif() && !$this->optimized_file_exists($this->get_optimized_url($normalized_url, 'avif'))) {
            $this->generate_avif($source_path, 0);
        }

        if ($this->get_setting('jxl_enabled', false) && $this->can_generate_jxl() && !$this->optimized_file_exists($this->get_optimized_url($normalized_url, 'jxl'))) {
            $this->generate_jxl($source_path, 0);
        }
    }
    
    /**
     * Generate optimized images when attachment is created
     *
     * @param array $metadata      Attachment metadata
     * @param int   $attachment_id Attachment ID
     * @return array Modified metadata
     */
    public function generate_optimized_images(array $metadata, int $attachment_id): array {
        // Try to increase limits for image processing on shared hosting
        @ini_set('memory_limit', '512M');
        @set_time_limit(120);

        // Check if it's an image
        if (!wp_attachment_is_image($attachment_id)) {
            return $metadata;
        }

        // Get file info
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            return $metadata;
        }

        // Check if format is supported
        $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
        if (!in_array($extension, self::SUPPORTED_FORMATS)) {
            return $metadata;
        }

        // Check if file should be excluded
        if ($this->should_exclude_file($file_path, $attachment_id)) {
            return $metadata;
        }

        // Store original file size before any optimization
        $original_size = filesize($file_path);
        update_post_meta($attachment_id, '_prorank_original_size', $original_size);

        // Track format generation status
        $webp_generated = false;
        $avif_generated = false;
        $total_savings = 0;
        $original_source_saved = 0;

        // Backup original if enabled
        if ($this->get_setting('backup_originals', true) && !$this->backup_original_image($file_path, $attachment_id)) {
            update_post_meta($attachment_id, '_prorank_optimization_failed', '1');
            update_post_meta($attachment_id, '_prorank_optimization_failure_reason', 'backup_original_failed');
            return $metadata;
        }

        // Optimize original + sizes with advanced engine.
        // Skip on local-only mode (free plugin) — GD re-compression saves very little
        // and uses a lot of memory on shared hosting.
        if ($this->should_optimize_original_source()) {
            $source_optimization = $this->optimize_original_and_sizes($file_path, $attachment_id, $metadata);
            $metadata = $source_optimization['metadata'] ?? $metadata;
            $original_source_saved = (int) ($source_optimization['original_saved_bytes'] ?? 0);
            $total_savings = max($total_savings, $original_source_saved);
        }

        // Generate WebP if enabled
        // Determine if running in constrained environment (shared hosting, free plugin local mode)
        $is_local_mode = ($this->get_optimization_method() === 'local');

        // Generate WebP if enabled
        if ($this->get_setting('webp_enabled', false)) {
            $webp_success = $this->generate_webp($file_path, $attachment_id);
            if ($webp_success) {
                $webp_generated = true;
                $webp_path = $this->get_optimized_path($file_path, 'webp');
                if (file_exists($webp_path)) {
                    $webp_size = filesize($webp_path);
                    if ($webp_size < $original_size) {
                        $total_savings = max($total_savings, $original_size - $webp_size);
                    }
                }
            }

            // Generate WebP for sizes (skip in local mode to conserve memory)
            if (!$is_local_mode && !empty($metadata['sizes'])) {
                $base_dir = dirname($file_path);
                foreach ($metadata['sizes'] as $size => $size_data) {
                    if ($this->should_exclude_file($file_path, $attachment_id, $size)) {
                        continue;
                    }
                    $size_path = $base_dir . '/' . $size_data['file'];
                    if (file_exists($size_path)) {
                        if ($this->generate_webp($size_path, $attachment_id, $size)) {
                            $webp_generated = true;
                        }
                    }
                }
            }
        }

        // Generate AVIF if enabled (skip in local mode — GD AVIF is very slow)
        if (!$is_local_mode && $this->get_setting('avif_enabled', false) && $this->is_avif_supported()) {
            $avif_success = $this->generate_avif($file_path, $attachment_id);
            if ($avif_success) {
                $avif_generated = true;
                $avif_path = $this->get_optimized_path($file_path, 'avif');
                if (file_exists($avif_path)) {
                    $avif_size = filesize($avif_path);
                    if ($avif_size < $original_size) {
                        $total_savings = max($total_savings, $original_size - $avif_size);
                    }
                }
            }

            if (!empty($metadata['sizes'])) {
                $base_dir = dirname($file_path);
                foreach ($metadata['sizes'] as $size => $size_data) {
                    if ($this->should_exclude_file($file_path, $attachment_id, $size)) {
                        continue;
                    }
                    $size_path = $base_dir . '/' . $size_data['file'];
                    if (file_exists($size_path)) {
                        if ($this->generate_avif($size_path, $attachment_id, $size)) {
                            $avif_generated = true;
                        }
                    }
                }
            }
        }

        // Generate JPEG XL if enabled (skip in local mode — requires binary tools)
        $jxl_generated = false;
        if (!$is_local_mode && $this->get_setting('jxl_enabled', false) && $this->can_generate_jxl()) {
            $jxl_success = $this->generate_jxl($file_path, $attachment_id);
            if ($jxl_success) {
                $jxl_generated = true;
                $jxl_path = $this->get_optimized_path($file_path, 'jxl');
                if (file_exists($jxl_path)) {
                    $jxl_size = filesize($jxl_path);
                    if ($jxl_size < $original_size) {
                        $total_savings = max($total_savings, $original_size - $jxl_size);
                    }
                }
            }

            if (!empty($metadata['sizes'])) {
                $base_dir = dirname($file_path);
                foreach ($metadata['sizes'] as $size => $size_data) {
                    if ($this->should_exclude_file($file_path, $attachment_id, $size)) {
                        continue;
                    }
                    $size_path = $base_dir . '/' . $size_data['file'];
                    if (file_exists($size_path)) {
                        if ($this->generate_jxl($size_path, $attachment_id, $size)) {
                            $jxl_generated = true;
                        }
                    }
                }
            }
        }

        $has_real_optimization = $webp_generated || $avif_generated || $jxl_generated || $original_source_saved > 0 || $total_savings > 0;

        update_post_meta($attachment_id, '_prorank_bytes_saved', $total_savings);
        update_post_meta($attachment_id, '_prorank_original_source_bytes_saved', $original_source_saved);
        update_post_meta($attachment_id, '_prorank_webp_generated', $webp_generated ? '1' : '');
        update_post_meta($attachment_id, '_prorank_avif_generated', $avif_generated ? '1' : '');
        update_post_meta($attachment_id, '_prorank_jxl_generated', $jxl_generated ? '1' : '');

        if ($has_real_optimization) {
            update_post_meta($attachment_id, '_prorank_optimized', '1');
            update_post_meta($attachment_id, '_prorank_optimization_date', current_time('mysql'));
            $this->clear_attachment_no_gain_state($attachment_id);
            delete_post_meta($attachment_id, '_prorank_optimization_failed');
            delete_post_meta($attachment_id, '_prorank_optimization_failure_reason');
            delete_post_meta($attachment_id, '_prorank_optimization_attempts');
        } else {
            $this->mark_attachment_no_gain($attachment_id, 'no_effective_output');
        }

        return $metadata;
    }

    /**
     * Optimize original image and all generated sizes using the advanced engine.
     *
     * @param string $file_path     Original file path.
     * @param int    $attachment_id Attachment ID.
     * @param array  $metadata      Attachment metadata.
     * @return void
     */
    private function optimize_original_and_sizes(string $file_path, int $attachment_id, array $metadata): array {
        $engine = new OptimizationEngine();
        $options = [
            'compression_type' => $this->get_setting('compression_type', 'glossy'),
            'smart_compression' => $this->get_setting('smart_compression', true),
            'jpegli_enabled' => $this->get_setting('jpegli_enabled', false),
            'png_pipeline' => $this->get_setting('png_pipeline', false),
            'png_quality' => (int) $this->get_setting('png_quality', 80),
            'optimization_method' => $this->get_optimization_method(),
        ];

        $starting_size = file_exists($file_path) ? (int) filesize($file_path) : 0;
        $resize_result = $this->maybe_resize_original_source($file_path, $attachment_id, $metadata);
        $metadata = $resize_result['metadata'] ?? $metadata;

        $original_result = $engine->optimize_file_path($file_path, $options);
        $final_size = file_exists($file_path) ? (int) filesize($file_path) : $starting_size;
        $original_saved_bytes = max(0, $starting_size - $final_size);

        if (!empty($original_result['success']) && !empty($original_result['method'])) {
            update_post_meta($attachment_id, '_prorank_optimization_method', (string) $original_result['method']);
        } elseif (!empty($resize_result['bytes_saved'])) {
            update_post_meta($attachment_id, '_prorank_optimization_method', 'resize');
        }

        if (!empty($metadata['sizes'])) {
            $base_dir = dirname($file_path);
            foreach ($metadata['sizes'] as $size => $size_data) {
                if ($this->should_exclude_file($file_path, $attachment_id, $size)) {
                    continue;
                }
                $size_path = $base_dir . '/' . $size_data['file'];
                if (file_exists($size_path)) {
                    $engine->optimize_file_path($size_path, $options);
                }
            }
        }

        return [
            'original_saved_bytes' => $original_saved_bytes,
            'original_success' => $original_saved_bytes > 0 || !empty($original_result['success']),
            'metadata' => $metadata,
        ];
    }

    /**
     * Free plugin image optimization is local-only.
     *
     * @return string
     */
    private function get_optimization_method(): string {
        return 'local';
    }

    /**
     * Whether the pipeline should intentionally optimize the original attachment URL in place.
     *
     * @return bool
     */
    private function should_optimize_original_source(): bool {
        return (bool) $this->get_setting('optimize_original_source', false)
            || $this->should_resize_large_originals();
    }

    /**
     * Whether large original attachments should be downscaled in place.
     *
     * @return bool
     */
    private function should_resize_large_originals(): bool {
        return (bool) $this->get_setting('resize_large_enabled', false)
            && (int) $this->get_setting('resize_max_width', 0) > 0
            && (int) $this->get_setting('resize_max_height', 0) > 0;
    }

    /**
     * Resize oversized originals before format optimization so raw attachment URLs can shrink too.
     *
     * @param string $file_path     Original file path.
     * @param int    $attachment_id Attachment ID.
     * @param array  $metadata      Attachment metadata.
     * @return array<string,mixed>
     */
    private function maybe_resize_original_source(string $file_path, int $attachment_id, array $metadata): array {
        if (!$this->should_resize_large_originals() || !file_exists($file_path)) {
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        $size = wp_getimagesize($file_path);
        if (!$size || empty($size[0]) || empty($size[1])) {
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        $max_width = max(100, (int) $this->get_setting('resize_max_width', 2560));
        $max_height = max(100, (int) $this->get_setting('resize_max_height', 2560));
        if ((int) $size[0] <= $max_width && (int) $size[1] <= $max_height) {
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        if (!function_exists('wp_get_image_editor')) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
        }
        if (!function_exists('wp_tempnam')) {
            $file_api = ABSPATH . 'wp-admin/includes/file.php';
            if (file_exists($file_api)) {
                require_once $file_api;
            }
        }

        $temp_backup = function_exists('wp_tempnam')
            ? wp_tempnam(basename($file_path))
            : tempnam(sys_get_temp_dir(), 'prorank_resize_');
        if (!$temp_backup || !@copy($file_path, $temp_backup)) {
            if ($temp_backup) {
                \ProRank\SEO\Core\FilesystemHelper::delete_file($temp_backup);
            }
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        $editor = wp_get_image_editor($file_path);
        if (is_wp_error($editor)) {
            \ProRank\SEO\Core\FilesystemHelper::delete_file($temp_backup);
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        $filetype = wp_check_filetype($file_path);
        $mime_type = $filetype['type'] ?? '';
        $result = $editor->resize($max_width, $max_height, false);
        if (is_wp_error($result)) {
            @copy($temp_backup, $file_path);
            \ProRank\SEO\Core\FilesystemHelper::delete_file($temp_backup);
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        $saved = $editor->save($file_path, $mime_type !== '' ? $mime_type : null);
        if (is_wp_error($saved)) {
            @copy($temp_backup, $file_path);
            \ProRank\SEO\Core\FilesystemHelper::delete_file($temp_backup);
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        clearstatcache(true, $file_path);
        $original_size = (int) filesize($temp_backup);
        $new_size = file_exists($file_path) ? (int) filesize($file_path) : $original_size;
        if ($new_size >= $original_size) {
            @copy($temp_backup, $file_path);
            \ProRank\SEO\Core\FilesystemHelper::delete_file($temp_backup);
            return [
                'bytes_saved' => 0,
                'metadata' => $metadata,
            ];
        }

        \ProRank\SEO\Core\FilesystemHelper::delete_file($temp_backup);
        $metadata = $this->sync_resized_original_metadata($metadata, $file_path);
        wp_update_attachment_metadata($attachment_id, $metadata);

        return [
            'bytes_saved' => max(0, $original_size - $new_size),
            'metadata' => $metadata,
        ];
    }

    /**
     * Keep attachment metadata aligned after shrinking the original file in place.
     *
     * @param array  $metadata  Existing attachment metadata.
     * @param string $file_path Original file path.
     * @return array
     */
    private function sync_resized_original_metadata(array $metadata, string $file_path): array {
        $size = wp_getimagesize($file_path);
        if (!$size || empty($size[0]) || empty($size[1])) {
            return $metadata;
        }

        $full_width = (int) $size[0];
        $full_height = (int) $size[1];
        $metadata['width'] = $full_width;
        $metadata['height'] = $full_height;

        if (!empty($metadata['sizes']) && is_array($metadata['sizes'])) {
            $base_dir = dirname($file_path);
            foreach ($metadata['sizes'] as $size_name => $size_data) {
                $size_width = (int) ($size_data['width'] ?? 0);
                $size_height = (int) ($size_data['height'] ?? 0);
                if ($size_width > $full_width || $size_height > $full_height) {
                    if (!empty($size_data['file'])) {
                        \ProRank\SEO\Core\FilesystemHelper::delete_file($base_dir . '/' . $size_data['file']);
                    }
                    unset($metadata['sizes'][$size_name]);
                }
            }
        }

        return $metadata;
    }

    /**
     * Send Client Hints headers to enable DPR/Width/Viewport hints.
     *
     * @return void
     */
    public function send_client_hints_headers(): void {
        if (is_admin() || headers_sent()) {
            return;
        }

        $hints = [];
        if ($this->get_setting('client_hints_dpr', true)) {
            $hints[] = 'DPR';
        }
        if ($this->get_setting('client_hints_width', true)) {
            $hints[] = 'Width';
        }
        if ($this->get_setting('client_hints_viewport', true)) {
            $hints[] = 'Viewport-Width';
        }
        if ($this->get_setting('client_hints_save_data', false)) {
            $hints[] = 'Save-Data';
        }

        if (!$hints) {
            return;
        }

        header('Accept-CH: ' . implode(', ', $hints));
        header('Accept-CH-Lifetime: 86400');
        header('Vary: ' . implode(', ', array_merge(['Accept'], $hints)), false);
    }
    
    /**
     * Generate WebP version of image
     *
     * @param string $source_path   Source image path
     * @param int    $attachment_id Attachment ID
     * @param string $size          Image size name
     * @return bool Success status
     */
    private function generate_webp(string $source_path, int $attachment_id, string $size = ''): bool {
        if (!$this->is_supported_source_path($source_path)) {
            return false;
        }

        // Check if GD or Imagick supports WebP
        if (!$this->can_generate_webp()) {
            return false;
        }
        
        $webp_path = $this->get_optimized_path($source_path, 'webp');
        
        // Skip if already exists
        if (file_exists($webp_path)) {
            return true;
        }
        
        // Create directory if needed
        $dir = dirname($webp_path);
        if (!is_dir($dir)) {
            wp_mkdir_p($dir);
        }
        
        // Get quality setting based on compression type
        $compression_type = $this->get_setting('compression_type', 'lossy');
        $quality = self::COMPRESSION_TYPES[$compression_type]['webp_quality'] ?? self::DEFAULT_WEBP_QUALITY;
        
        // Override with custom quality if SmartCompression is disabled
        if (!$this->get_setting('smart_compression', true)) {
            $quality = $this->get_setting('webp_quality', self::DEFAULT_WEBP_QUALITY);
        }

        $quality = $this->adjust_smart_format_quality($source_path, 'webp', (int) $quality);
        
        // Try to generate WebP
        $success = false;

        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($source_path);
                $imagick->setImageFormat('webp');
                $imagick->setImageCompressionQuality($quality);
                $success = $imagick->writeImage($webp_path);
                $imagick->destroy();
            } catch (\Exception $e) {
                // Fall back to GD
            }
        }

        if (!$success && function_exists('imagewebp')) {
            $image = $this->load_image_gd($source_path);
            if ($image) {
                $success = imagewebp($image, $webp_path, $quality);
                imagedestroy($image);
            }
        }

        if ($success && file_exists($webp_path) && filesize($webp_path) === 0) {
            \ProRank\SEO\Core\FilesystemHelper::delete_file($webp_path);
            return false;
        }

        // Size validation: WebP must be smaller than source, otherwise discard
        if ($success && file_exists($webp_path)) {
            $source_size = filesize($source_path);
            $webp_size = filesize($webp_path);
            if ($webp_size >= $source_size) {
                \ProRank\SEO\Core\FilesystemHelper::delete_file($webp_path);
                return false;
            }
        }

        if ($success && $attachment_id > 0) {
            // Store metadata
            $meta_key = '_prorank_webp_path' . ($size ? '_' . $size : '');
            update_post_meta($attachment_id, $meta_key, $webp_path);

            // Update statistics
            $this->update_optimization_stats($source_path, $webp_path, 'webp');
        }

        return $success;
    }
    
    /**
     * Optimize image via cloud service with retry mechanism
     *
     * @param string $source_path Source image path
     * @param string $target_path Target image path
     * @param string $format Target format (webp/avif)
     * @param int $quality Quality setting
     * @return bool Success status
     */
    private function optimize_via_cloud(string $source_path, string $target_path, string $format, int $quality): bool {
        // Check license tier / credits for cloud optimization
        $plugin = \ProRank\SEO\Plugin::get_instance();
        if (!$plugin || !$plugin->is_initialized()) {
            return false;
        }
        
        $license_manager = $plugin->license();
        $quota_status = $this->get_quota_status();
        if (($quota_status['status'] ?? '') !== 'active') {
            prorank_log('[ProRank Image Optimization] Cloud optimization unavailable: ' . ($quota_status['message'] ?? 'No active credits'));
            return false;
        }
        
        $provider = $this->get_setting('cloud_provider', 'prorank');
        $providers = $this->getCloudProviders();
        $endpoint = $providers[$provider]['endpoint'] ?? null;
        
        if (!$endpoint) {
            return false;
        }
        
        $auth_token = $this->get_cloud_auth_token();
        if (!$auth_token) {
            prorank_log('[ProRank Image Optimization] No valid JWT token available for cloud optimization');
            return false;
        }
        
        // Prepare request metadata
        $headers = $this->build_cloud_auth_headers($auth_token);
        
        // Add parameters
        $params = [
            'format' => $format,
            'quality' => (string) $quality,
            'compression_type' => $this->get_setting('compression_type', 'lossy'),
            'smart_compression' => $this->get_setting('smart_compression', true) ? '1' : '0',
        ];
        
        // Retry mechanism with exponential backoff
        $max_retries = 3;
        $retry_delay = 1000; // Start with 1 second
        $attempt = 0;
        $success = false;
        $last_error = '';
        $optimized_image = null;
        
        while ($attempt < $max_retries && !$success) {
            $attempt++;

            // Make the request (streaming for larger files, fallback for compatibility)
            $request_result = $this->send_cloud_multipart_request(
                $endpoint,
                $source_path,
                $headers,
                $params,
                30 + ($attempt * 10) // Increase timeout with each retry
            );

            if (!$request_result['ok']) {
                $last_error = $request_result['error'] ?: 'Cloud request failed';
                
                // Log error for monitoring
                prorank_log(sprintf(
                    '[ProRank Image Optimization] Cloud request failed (attempt %d/%d): %s',
                    $attempt,
                    $max_retries,
                    $last_error
                ));
                
                // If not last attempt, wait before retry
                if ($attempt < $max_retries) {
                    usleep($retry_delay * 1000); // Convert to microseconds
                    $retry_delay *= 2; // Exponential backoff
                }
                continue;
            }

            $response_code = (int) $request_result['status_code'];
            $response_body = (string) $request_result['body'];
            $response_headers = is_array($request_result['headers'] ?? null) ? $request_result['headers'] : [];
            
            // Success
            if ($response_code === 200) {
                $optimized_image = $response_body;
                $success = true;
                break;
            }
            
            $decoded_error = json_decode($response_body, true);
            $quota_error_detected =
                is_array($decoded_error)
                && (
                    isset($decoded_error['available_credits'])
                    || isset($decoded_error['remaining'])
                    || isset($decoded_error['remaining_quota'])
                    || isset($decoded_error['total_available'])
                    || isset($decoded_error['code'])
                )
                && (
                    stripos((string) ($decoded_error['error'] ?? $decoded_error['message'] ?? ''), 'quota') !== false
                    || stripos((string) ($decoded_error['error'] ?? $decoded_error['message'] ?? ''), 'credit') !== false
                    || in_array((string) ($decoded_error['code'] ?? ''), ['quota_exhausted', 'credits_required'], true)
                );

            // Quota exhausted or purchased credits unavailable - don't retry.
            if (($response_code === 402 || $response_code === 429) && $quota_error_detected) {
                $license_status = $license_manager->get_license_status_snapshot();
                $tier = $license_status['tier'] ?? $license_manager->get_tier();
                $normalized_tier = strpos($tier, '_plus') !== false ? str_replace('_plus', '', $tier) : $tier;
                if (!$license_manager->has_valid_license_status()) {
                    $normalized_tier = 'free';
                }
                $period = $this->resolve_quota_period(
                    $license_status,
                    $normalized_tier === 'free' && (bool) $this->get_setting('cloud_opt_in', false),
                    $this->normalize_opt_in_timestamp($this->get_setting('cloud_opt_in_at', null))
                );

                $quota_message = (string) ($decoded_error['message'] ?? $decoded_error['error'] ?? __('Cloud credits are not available', 'prorank-seo'));
                set_transient('prorank_image_quota_exceeded', [
                    'message' => $quota_message,
                    'remaining' => 0,
                    'next_reset' => $decoded_error['next_reset'] ?? gmdate('c', $period['end']),
                ], HOUR_IN_SECONDS);

                prorank_log(sprintf(
                    '[ProRank Image Optimization] Cloud quota exhausted: %s',
                    $quota_message
                ));

                $last_error = $quota_message;
                break;
            }

            // Rate limiting - wait longer
            if ($response_code === 429) {
                $retry_after_value = $response_headers['retry-after'] ?? '';
                if (is_array($retry_after_value)) {
                    $retry_after_value = reset($retry_after_value);
                }
                $retry_after_seconds = is_numeric($retry_after_value) ? (int) $retry_after_value : 0;
                $wait_time = $retry_after_seconds > 0 ? $retry_after_seconds * 1000000 : $retry_delay * 2000;
                
                prorank_log(sprintf(
                    '[ProRank Image Optimization] Rate limited (attempt %d/%d), waiting %d seconds',
                    $attempt,
                    $max_retries,
                    $wait_time / 1000000
                ));
                
                if ($attempt < $max_retries) {
                    usleep($wait_time);
                    $retry_delay = $wait_time / 1000; // Update delay for next iteration
                }
                continue;
            }
            
            // Quota exceeded - don't retry
            if ($response_code === 402) {
                $error_body = $response_body;
                $error_data = json_decode($error_body, true);
                $license_status = $license_manager->get_license_status_snapshot();
                $tier = $license_status['tier'] ?? $license_manager->get_tier();
                $normalized_tier = strpos($tier, '_plus') !== false ? str_replace('_plus', '', $tier) : $tier;
                if (!$license_manager->has_valid_license_status()) {
                    $normalized_tier = 'free';
                }
                $period = $this->resolve_quota_period(
                    $license_status,
                    $normalized_tier === 'free' && (bool) $this->get_setting('cloud_opt_in', false),
                    $this->normalize_opt_in_timestamp($this->get_setting('cloud_opt_in_at', null))
                );
                
                // Store quota status for display
                set_transient('prorank_image_quota_exceeded', [
                    'message' => $error_data['message'] ?? 'Yearly quota exceeded',
                    'remaining' => $error_data['remaining'] ?? 0,
                    'next_reset' => $error_data['next_reset'] ?? gmdate('c', $period['end']),
                ], HOUR_IN_SECONDS);
                
                prorank_log(sprintf(
                    '[ProRank Image Optimization] Quota exceeded: %s',
                    $error_data['message'] ?? 'Yearly quota exceeded'
                ));
                $last_error = $error_data['message'] ?? 'Yearly quota exceeded';
                break;
            }
            
            // License invalid
            if ($response_code === 401) {
                prorank_log('[ProRank Image Optimization] License authentication failed');
                $last_error = 'License authentication failed';
                
                // Clear cached license status
                delete_transient('prorank_license_status');
                break;
            }
            
            // Other client errors - don't retry
            if ($response_code >= 400 && $response_code < 500) {
                $error_body = $response_body;
                prorank_log(sprintf(
                    '[ProRank Image Optimization] Client error %d: %s',
                    $response_code,
                    substr($error_body, 0, 200)
                ));
                $last_error = sprintf('HTTP %d: %s', $response_code, substr((string) $error_body, 0, 200));
                break;
            }
            
            // Server error - retry
            if ($response_code >= 500) {
                prorank_log(sprintf(
                    '[ProRank Image Optimization] Server error %d (attempt %d/%d)',
                    $response_code,
                    $attempt,
                    $max_retries
                ));
                
                if ($attempt < $max_retries) {
                    usleep($retry_delay * 1000);
                    $retry_delay *= 2;
                }
                $last_error = sprintf('Server error %d', $response_code);
                continue;
            }

            $last_error = sprintf('Unexpected HTTP status %d', $response_code);
        }
        
        if (!$success || !$optimized_image) {
            // Store failed optimization in queue for later retry
            $this->queue_failed_optimization($source_path, $format, $quality, $last_error);
            return false;
        }
        if (empty($optimized_image)) {
            return false;
        }
        
        // Save the optimized image
        $result = file_put_contents($target_path, $optimized_image);
        if ($result !== false) {
            // Force quota refresh on next read so dashboard reflects recent cloud usage quickly.
            $this->invalidate_quota_usage_cache();
            delete_transient('prorank_image_quota_exceeded');
            return true;
        }

        return false;
    }

    /**
     * Determine if cloud upload should use streaming transport.
     *
     * @param string $file_path Source file path.
     * @return bool
     */
    private function should_stream_cloud_upload(string $file_path): bool {
        $threshold = (int) apply_filters('prorank_cloud_streaming_threshold_bytes', 5 * 1024 * 1024, $file_path);
        $file_size = @filesize($file_path);
        if ($file_size === false) {
            return false;
        }

        return $file_size >= max(0, $threshold);
    }

    /**
     * Send multipart cloud request with streaming support and fallback.
     *
     * @param string $endpoint API endpoint.
     * @param string $file_path File path.
     * @param array $headers Request headers.
     * @param array $params Multipart params.
     * @param int $timeout Request timeout.
     * @return array{ok:bool,status_code:int,body:string,headers:array,error:string}
     */
    private function send_cloud_multipart_request(string $endpoint, string $file_path, array $headers, array $params, int $timeout): array {
        return $this->send_cloud_multipart_request_via_wp($endpoint, $file_path, $headers, $params, $timeout);
    }

    /**
     * Send multipart cloud request via cURL (streaming upload).
     *
     * @param string $endpoint API endpoint.
     * @param string $file_path File path.
     * @param array $headers Request headers.
     * @param array $params Multipart params.
     * @param int $timeout Request timeout.
     * @return array{ok:bool,status_code:int,body:string,headers:array,error:string}
     */
    private function send_cloud_multipart_request_via_curl(string $endpoint, string $file_path, array $headers, array $params, int $timeout): array {
        return $this->send_cloud_multipart_request_via_wp($endpoint, $file_path, $headers, $params, $timeout);
    }

    /**
     * Send multipart cloud request via WP HTTP API.
     *
     * @param string $endpoint API endpoint.
     * @param string $file_path File path.
     * @param array $headers Request headers.
     * @param array $params Multipart params.
     * @param int $timeout Request timeout.
     * @return array{ok:bool,status_code:int,body:string,headers:array,error:string}
     */
    private function send_cloud_multipart_request_via_wp(string $endpoint, string $file_path, array $headers, array $params, int $timeout): array {
        $file_data = file_get_contents($file_path);
        if ($file_data === false) {
            return [
                'ok' => false,
                'status_code' => 0,
                'body' => '',
                'headers' => [],
                'error' => 'Failed to read source file',
            ];
        }

        $boundary = wp_generate_password(24);
        $body = '';

        $body .= '--' . $boundary . "\r\n";
        $body .= 'Content-Disposition: form-data; name="image"; filename="' . basename($file_path) . '"' . "\r\n";
        $filetype = wp_check_filetype($file_path);
        $body .= 'Content-Type: ' . ($filetype['type'] ?: 'application/octet-stream') . "\r\n\r\n";
        $body .= $file_data . "\r\n";

        foreach ($params as $key => $value) {
            $body .= '--' . $boundary . "\r\n";
            $body .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n\r\n";
            $body .= (string) $value . "\r\n";
        }

        $body .= '--' . $boundary . '--';

        $request_headers = $headers;
        $request_headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;

        $response = wp_remote_post($endpoint, [
            'headers' => $request_headers,
            'body' => $body,
            'timeout' => $timeout,
        ]);

        if (is_wp_error($response)) {
            return [
                'ok' => false,
                'status_code' => 0,
                'body' => '',
                'headers' => [],
                'error' => $response->get_error_message(),
            ];
        }

        $normalized_headers = [];
        $raw_headers = wp_remote_retrieve_headers($response);
        if (is_array($raw_headers) || $raw_headers instanceof \Traversable) {
            foreach ($raw_headers as $key => $value) {
                $normalized_headers[strtolower((string) $key)] = is_array($value) ? implode(', ', $value) : (string) $value;
            }
        }

        return [
            'ok' => true,
            'status_code' => (int) wp_remote_retrieve_response_code($response),
            'body' => (string) wp_remote_retrieve_body($response),
            'headers' => $normalized_headers,
            'error' => '',
        ];
    }
    
    /**
     * Check server WebP support
     *
     * @return array Support information
     */
    public function check_webp_support(): array {
        $support = [
            'gd' => false,
            'imagick' => false,
            'details' => [],
        ];
        
        // Check GD
        if (function_exists('gd_info')) {
            $gd_info = gd_info();
            $support['gd'] = !empty($gd_info['WebP Support']);
            $support['details']['gd_version'] = $gd_info['GD Version'] ?? 'Unknown';
        }
        
        // Check Imagick
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick();
                $formats = $imagick->queryFormats('WEBP');
                $support['imagick'] = !empty($formats);
                $support['details']['imagick_version'] = \Imagick::getVersion()['versionString'] ?? 'Unknown';
            } catch (\Exception $e) {
                $support['details']['imagick_error'] = $e->getMessage();
            }
        }
        
        return $support;
    }
    
    /**
     * Generate AVIF version of image
     *
     * @param string $source_path   Source image path
     * @param int    $attachment_id Attachment ID
     * @param string $size          Image size name
     * @return bool Success status
     */
    private function generate_avif(string $source_path, int $attachment_id, string $size = ''): bool {
        if (!$this->is_supported_source_path($source_path)) {
            return false;
        }

        // Check if system supports AVIF
        if (!$this->can_generate_avif()) {
            return false;
        }
        
        $avif_path = $this->get_optimized_path($source_path, 'avif');
        
        // Skip if already exists
        if (file_exists($avif_path)) {
            return true;
        }
        
        // Create directory if needed
        $dir = dirname($avif_path);
        if (!is_dir($dir)) {
            wp_mkdir_p($dir);
        }
        
        // Get quality setting based on compression type
        $compression_type = $this->get_setting('compression_type', 'lossy');
        $quality = self::COMPRESSION_TYPES[$compression_type]['avif_quality'] ?? self::DEFAULT_AVIF_QUALITY;

        // Override with custom quality if SmartCompression is disabled
        if (!$this->get_setting('smart_compression', true)) {
            $quality = $this->get_setting('avif_quality', self::DEFAULT_AVIF_QUALITY);
        }

        $quality = $this->adjust_smart_format_quality($source_path, 'avif', (int) $quality);

        // Try to generate AVIF
        $success = false;

        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($source_path);
                $formats = $imagick->queryFormats('AVIF');
                if (!empty($formats)) {
                    $imagick->setImageFormat('avif');
                    $imagick->setImageCompressionQuality($quality);
                    $success = $imagick->writeImage($avif_path);
                }
                $imagick->destroy();
            } catch (\Exception $e) {
                // AVIF not supported
            }
        }

        if (!$success && function_exists('imageavif')) {
            $image = $this->load_image_gd($source_path);
            if ($image) {
                $success = imageavif($image, $avif_path, $quality);
                imagedestroy($image);
            }
        }

        if ($success && file_exists($avif_path) && filesize($avif_path) === 0) {
            \ProRank\SEO\Core\FilesystemHelper::delete_file($avif_path);
            return false;
        }

        // Size validation: AVIF must be smaller than source, otherwise discard
        if ($success && file_exists($avif_path)) {
            $source_size = filesize($source_path);
            $avif_size = filesize($avif_path);
            if ($avif_size >= $source_size) {
                \ProRank\SEO\Core\FilesystemHelper::delete_file($avif_path);
                return false;
            }
        }

        if ($success && $attachment_id > 0) {
            // Store metadata
            $meta_key = '_prorank_avif_path' . ($size ? '_' . $size : '');
            update_post_meta($attachment_id, $meta_key, $avif_path);

            // Update statistics
            $this->update_optimization_stats($source_path, $avif_path, 'avif');
        }

        return $success;
    }

    /**
     * Tune modern-format quality for small flat PNG assets when Smart Compression is enabled.
     *
     * Badges, logos, and other UI-style PNGs often remain larger than necessary
     * after WebP conversion, and PageSpeed flags them even when responsive
     * delivery is correct. AVIF handles these small graphics much better at a
     * lower quality setting while preserving acceptable visual fidelity.
     *
     * @param string $source_path Source image path.
     * @param string $format      Target format.
     * @param int    $quality     Current quality.
     * @return int
     */
    private function adjust_smart_format_quality(string $source_path, string $format, int $quality): int {
        if (!$this->get_setting('smart_compression', true)) {
            return $quality;
        }

        $format = strtolower($format);
        if (!in_array($format, ['webp', 'avif'], true)) {
            return $quality;
        }

        $extension = strtolower((string) pathinfo($source_path, PATHINFO_EXTENSION));
        if ($extension !== 'png') {
            return $quality;
        }

        $dimensions = function_exists('wp_getimagesize') ? wp_getimagesize($source_path) : @getimagesize($source_path);
        if (!$dimensions || empty($dimensions[0]) || empty($dimensions[1])) {
            return $quality;
        }

        $width = (int) $dimensions[0];
        $height = (int) $dimensions[1];
        if ($width <= 0 || $height <= 0 || $width > 640 || $height > 320) {
            return $quality;
        }

        $source_size = file_exists($source_path) ? (int) filesize($source_path) : 0;
        if ($source_size <= 0 || $source_size > 128 * 1024) {
            return $quality;
        }

        if ($format === 'avif') {
            return min($quality, (int) apply_filters('prorank_smart_png_badge_avif_quality', 28, $source_path, $width, $height));
        }

        return min($quality, (int) apply_filters('prorank_smart_png_badge_webp_quality', 55, $source_path, $width, $height));
    }

    /**
     * Generate JPEG XL version of an image
     *
     * @param string $source_path   Source image path
     * @param int    $attachment_id Attachment ID
     * @param string $size          Image size name
     * @return bool Success status
     */
    private function generate_jxl(string $source_path, int $attachment_id, string $size = ''): bool {
        $jxl_path = $this->get_optimized_path($source_path, 'jxl');

        // Skip if already exists
        if (file_exists($jxl_path)) {
            return true;
        }

        // Create directory if needed
        $dir = dirname($jxl_path);
        if (!is_dir($dir)) {
            wp_mkdir_p($dir);
        }

        // Get quality setting
        $compression_type = $this->get_setting('compression_type', 'lossy');
        $quality = self::COMPRESSION_TYPES[$compression_type]['jxl_quality'] ?? self::DEFAULT_JXL_QUALITY;

        $success = false;

        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($source_path);
                $formats = $imagick->queryFormats('JXL');
                if (!empty($formats)) {
                    $imagick->setImageFormat('jxl');
                    $imagick->setImageCompressionQuality($quality);
                    $success = $imagick->writeImage($jxl_path);
                }
                $imagick->destroy();
            } catch (\Exception $e) {
                // JXL not supported
            }
        }

        if ($success && $attachment_id > 0) {
            $meta_key = '_prorank_jxl_path' . ($size ? '_' . $size : '');
            update_post_meta($attachment_id, $meta_key, $jxl_path);
            $this->update_optimization_stats($source_path, $jxl_path, 'jxl');
        }

        return $success;
    }

    /**
     * Convert quality percentage to JXL distance value
     * JXL uses distance where 0 = lossless, higher = more compression
     *
     * @param int $quality Quality 0-100
     * @return string Distance value
     */
    private function quality_to_jxl_distance(int $quality): string {
        if ($quality >= 100) {
            return '0'; // Lossless
        }
        // Map 0-99 quality to 0.1-3.0 distance (lower quality = higher distance)
        $distance = 0.1 + ((100 - $quality) / 100) * 2.9;
        return number_format($distance, 2);
    }

    /**
     * Check if cjxl CLI tool is available
     *
     * @return bool
     */
    private function has_jxl_cli(): bool {
        return false;
    }

    /**
     * Load image using GD
     *
     * @param string $path Image path
     * @return resource|false GD image resource
     */
    private function load_image_gd(string $path) {
        $info = wp_getimagesize($path);
        if (!$info) {
            return false;
        }
        
        switch ($info['mime']) {
            case 'image/jpeg':
                return imagecreatefromjpeg($path);
            case 'image/png':
                $image = imagecreatefrompng($path);
                if (!$image) {
                    return false;
                }

                if (function_exists('imageistruecolor') && function_exists('imagepalettetotruecolor') && !imageistruecolor($image)) {
                    imagepalettetotruecolor($image);
                }

                if (function_exists('imagealphablending')) {
                    imagealphablending($image, false);
                }

                if (function_exists('imagesavealpha')) {
                    imagesavealpha($image, true);
                }

                return $image;
            default:
                return false;
        }
    }

    /**
     * Confirm the source asset is a raster format this module officially supports.
     *
     * @param string $path Image path
     * @return bool
     */
    private function is_supported_source_path(string $path): bool {
        $info = wp_getimagesize($path);
        if (!$info || empty($info['mime']) || !is_string($info['mime'])) {
            return false;
        }

        return in_array(strtolower($info['mime']), self::SUPPORTED_MIME_TYPES, true);
    }
    
    /**
     * Get optimized image path
     *
     * @param string $original_path Original image path
     * @param string $format        Target format (webp/avif)
     * @return string Optimized image path
     */
    public function get_optimized_path(string $original_path, string $format): string {
        $pathinfo = pathinfo($original_path);
        return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '.' . $format;
    }
    
    /**
     * Delete optimized images when original is deleted
     *
     * @param int $attachment_id Attachment ID
     * @return void
     */
    public function delete_optimized_images(int $attachment_id): void {
        foreach ($this->get_optimized_path_meta_keys($attachment_id) as $meta_key) {
            $path = get_post_meta($attachment_id, $meta_key, true);
            if ($path && file_exists($path)) {
                \ProRank\SEO\Core\FilesystemHelper::delete_file($path);
            }

            delete_post_meta($attachment_id, $meta_key);
        }
    }
    
    /**
     * Add optimized formats to srcset
     *
     * @param array  $sources       Array of image sources
     * @param array  $size_array    Requested size
     * @param string $image_src     Image source URL
     * @param array  $image_meta    Image metadata
     * @param int    $attachment_id Attachment ID
     * @return array Modified sources
     */
    public function add_optimized_to_srcset(array $sources, array $size_array, string $image_src, array $image_meta, int $attachment_id): array {
        // Not used anymore - we're using picture element instead
        return $sources;
    }
    
    /**
     * Check if WebP generation is supported
     *
     * @return bool
     */
    private function can_generate_webp(): bool {
        if (function_exists('imagewebp')) {
            return true;
        }

        // Check Imagick
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            $imagick = new \Imagick();
            $formats = $imagick->queryFormats('WEBP');
            return !empty($formats);
        }

        return false;
    }
    
    /**
     * Check if AVIF generation is supported
     *
     * @return bool
     */
    private function can_generate_avif(): bool {
        if (function_exists('imageavif')) {
            return true;
        }

        // Check Imagick
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            $imagick = new \Imagick();
            $formats = $imagick->queryFormats('AVIF');
            return !empty($formats);
        }

        return false;
    }
    
    /**
     * Check if AVIF is supported by server
     *
     * @return bool
     */
    private function is_avif_supported(): bool {
        return $this->can_generate_avif();
    }

    /**
     * Check if JPEG XL generation is supported
     *
     * @return bool
     */
    private function can_generate_jxl(): bool {
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick();
                $formats = $imagick->queryFormats('JXL');
                if (!empty($formats)) {
                    return true;
                }
            } catch (\Exception $e) {
                return false;
            }
        }

        return false;
    }
    
    /**
     * Update optimization statistics
     *
     * @param string $original_path Original file path
     * @param string $optimized_path Optimized file path
     * @param string $format Format type
     * @return void
     */
    private function update_optimization_stats(string $original_path, string $optimized_path, string $format): void {
        $original_size = filesize($original_path);
        $optimized_size = filesize($optimized_path);
        
        if ($original_size && $optimized_size) {
            $savings = max(0, $original_size - $optimized_size);
            
            // Update global stats
            $stats = get_option('prorank_image_optimization_stats', [
                'total_images' => 0,
                'total_savings' => 0,
                'webp_count' => 0,
                'avif_count' => 0,
            ]);

            if (!is_array($stats)) {
                $stats = [];
            }
            $stats['total_images'] = (int) ($stats['total_images'] ?? 0);
            $stats['total_savings'] = (int) ($stats['total_savings'] ?? 0);
            $count_key = $format . '_count';
            $stats[$count_key] = (int) ($stats[$count_key] ?? 0);
            
            $stats['total_images']++;
            $stats['total_savings'] += $savings;
            $stats[$count_key]++;
            
            update_option('prorank_image_optimization_stats', $stats, false);
        }
    }
    
    /**
     * Handle bulk conversion AJAX request
     *
     * @return void
     */
    public function handle_bulk_conversion(): void {
        // Check nonce
        if (!check_ajax_referer('prorank_image_optimization', 'nonce', false)) {
            wp_send_json_error(__('Invalid nonce', 'prorank-seo'));
        }
        
        // Check capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('Insufficient permissions', 'prorank-seo'));
        }
        
        // Get batch of images
        $args = [
            'post_type' => 'attachment',
            'post_mime_type' => 'image',
            'post_status' => 'inherit',
            'posts_per_page' => 5,
            'meta_query' => [
                [
                    'key' => '_prorank_optimized',
                    'compare' => 'NOT EXISTS',
                ],
            ],
        ];
        
        $attachments = get_posts($args);
        
        if (empty($attachments)) {
            wp_send_json_success([
                'complete' => true,
                'message' => __('All images have been optimized', 'prorank-seo'),
            ]);
        }
        
        $processed = 0;
        foreach ($attachments as $attachment) {
            $metadata = wp_get_attachment_metadata($attachment->ID);
            if ($metadata) {
                $this->generate_optimized_images($metadata, $attachment->ID);
                $processed++;
            }
        }
        
        // Get remaining count
        $remaining_args = $args;
        $remaining_args['posts_per_page'] = -1;
        $remaining_args['fields'] = 'ids';
        $remaining = count(get_posts($remaining_args));
        
        wp_send_json_success([
            'complete' => false,
            'processed' => $processed,
            'remaining' => $remaining,
            'message' => sprintf(
                /* translators: %d: number of images processed */
                __('Processed %1$d images', 'prorank-seo'),
                $processed
            ),
        ]);
    }
    
    /**
     * Register custom image sizes
     *
     * @return void
     */
    public function register_image_sizes(): void {
        if (
            !$this->get_setting('serve_scaled_images', true)
            && !$this->get_setting('enable_responsive_images', true)
        ) {
            return;
        }

        foreach (self::RESPONSIVE_SUBSIZES as $width) {
            add_image_size('prorank-responsive-' . $width, $width, 0, false);
        }
    }
    
    /**
     * Get optimization statistics
     *
     * @return array Statistics
     */
    public function get_stats(): array {
        $stats = get_option('prorank_image_optimization_stats', [
            'total_images' => 0,
            'total_savings' => 0,
            'webp_count' => 0,
            'avif_count' => 0,
            'bytes_saved' => 0,
        ]);

        $retry_limit = $this->get_bulk_retry_limit();

        // Add capability info
        $stats['can_generate_webp'] = $this->can_generate_webp();
        $stats['can_generate_avif'] = $this->can_generate_avif();
        $stats['can_generate_jxl'] = $this->can_generate_jxl();
        
        global $wpdb;

        $mime_placeholders = implode(', ', array_fill(0, count(self::SUPPORTED_MIME_TYPES), '%s'));
        $modern_mime_placeholders = implode(', ', array_fill(0, count(self::MODERN_ORIGINAL_MIME_TYPES), '%s'));
        $excluded_mime_types = array_merge(self::SUPPORTED_MIME_TYPES, self::MODERN_ORIGINAL_MIME_TYPES);
        $excluded_mime_placeholders = implode(', ', array_fill(0, count($excluded_mime_types), '%s'));

        // Count only images that actually produced a modern output or real savings.
        $optimized_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND p.post_mime_type IN ({$mime_placeholders})
                 AND EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm
                     WHERE pm.post_id = p.ID
                     AND (
                         (pm.meta_key = '_prorank_webp_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_avif_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_jxl_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_bytes_saved' AND CAST(pm.meta_value AS UNSIGNED) > 0)
                     )
                 )",
                ...self::SUPPORTED_MIME_TYPES
            )
        );

        // Count source images this module can actually convert.
        $source_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM {$wpdb->posts}
                 WHERE post_type = 'attachment'
                 AND post_status <> 'trash'
                 AND post_mime_type IN ({$mime_placeholders})",
                ...self::SUPPORTED_MIME_TYPES
            )
        );

        // Count modern originals separately so the UI can show them as already optimized.
        $modern_original_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM {$wpdb->posts}
                 WHERE post_type = 'attachment'
                 AND post_status <> 'trash'
                 AND post_mime_type IN ({$modern_mime_placeholders})",
                ...self::MODERN_ORIGINAL_MIME_TYPES
            )
        );

        $unsupported_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM {$wpdb->posts}
                 WHERE post_type = 'attachment'
                 AND post_status <> 'trash'
                 AND post_mime_type LIKE %s
                 AND post_mime_type NOT IN ({$excluded_mime_placeholders})",
                $wpdb->esc_like('image/') . '%',
                ...$excluded_mime_types
            )
        );

        $current_saved_bytes = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COALESCE(SUM(CAST(pm.meta_value AS UNSIGNED)), 0)
                 FROM {$wpdb->posts} p
                 INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND p.post_mime_type IN ({$mime_placeholders})
                 AND pm.meta_key = '_prorank_bytes_saved'",
                ...self::SUPPORTED_MIME_TYPES
            )
        );

        $retryable_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND p.post_mime_type IN ({$mime_placeholders})
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm
                     WHERE pm.post_id = p.ID
                     AND (
                         (pm.meta_key = '_prorank_webp_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_avif_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_jxl_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_bytes_saved' AND CAST(pm.meta_value AS UNSIGNED) > 0)
                     )
                 )
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_no_gain
                     WHERE pm_no_gain.post_id = p.ID
                     AND (
                         (pm_no_gain.meta_key = '_prorank_optimization_no_gain' AND pm_no_gain.meta_value = '1')
                         OR (pm_no_gain.meta_key = '_prorank_optimization_failure_reason' AND pm_no_gain.meta_value = 'no_effective_output')
                     )
                 )
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_attempts
                     WHERE pm_attempts.post_id = p.ID
                     AND pm_attempts.meta_key = '_prorank_optimization_attempts'
                     AND CAST(pm_attempts.meta_value AS UNSIGNED) >= %d
                 )",
                ...array_merge(self::SUPPORTED_MIME_TYPES, [$retry_limit])
            )
        );

        $no_gain_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND p.post_mime_type IN ({$mime_placeholders})
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm
                     WHERE pm.post_id = p.ID
                     AND (
                         (pm.meta_key = '_prorank_webp_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_avif_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_jxl_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_bytes_saved' AND CAST(pm.meta_value AS UNSIGNED) > 0)
                     )
                 )
                 AND EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_no_gain
                     WHERE pm_no_gain.post_id = p.ID
                     AND (
                         (pm_no_gain.meta_key = '_prorank_optimization_no_gain' AND pm_no_gain.meta_value = '1')
                         OR (pm_no_gain.meta_key = '_prorank_optimization_failure_reason' AND pm_no_gain.meta_value = 'no_effective_output')
                     )
                 )",
                ...self::SUPPORTED_MIME_TYPES
            )
        );

        $failed_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND p.post_mime_type IN ({$mime_placeholders})
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm
                     WHERE pm.post_id = p.ID
                     AND (
                         (pm.meta_key = '_prorank_webp_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_avif_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_jxl_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_bytes_saved' AND CAST(pm.meta_value AS UNSIGNED) > 0)
                     )
                 )
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_no_gain
                     WHERE pm_no_gain.post_id = p.ID
                     AND (
                         (pm_no_gain.meta_key = '_prorank_optimization_no_gain' AND pm_no_gain.meta_value = '1')
                         OR (pm_no_gain.meta_key = '_prorank_optimization_failure_reason' AND pm_no_gain.meta_value = 'no_effective_output')
                     )
                 )
                 AND EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_failed
                     WHERE pm_failed.post_id = p.ID
                     AND pm_failed.meta_key = '_prorank_optimization_failed'
                     AND pm_failed.meta_value = '1'
                 )",
                ...self::SUPPORTED_MIME_TYPES
            )
        );

        $exhausted_failed_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND p.post_mime_type IN ({$mime_placeholders})
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm
                     WHERE pm.post_id = p.ID
                     AND (
                         (pm.meta_key = '_prorank_webp_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_avif_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_jxl_generated' AND pm.meta_value = '1')
                         OR (pm.meta_key = '_prorank_bytes_saved' AND CAST(pm.meta_value AS UNSIGNED) > 0)
                     )
                 )
                 AND NOT EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_no_gain
                     WHERE pm_no_gain.post_id = p.ID
                     AND (
                         (pm_no_gain.meta_key = '_prorank_optimization_no_gain' AND pm_no_gain.meta_value = '1')
                         OR (pm_no_gain.meta_key = '_prorank_optimization_failure_reason' AND pm_no_gain.meta_value = 'no_effective_output')
                     )
                 )
                 AND EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_failed
                     WHERE pm_failed.post_id = p.ID
                     AND pm_failed.meta_key = '_prorank_optimization_failed'
                     AND pm_failed.meta_value = '1'
                 )
                 AND EXISTS (
                     SELECT 1 FROM {$wpdb->postmeta} pm_attempts
                     WHERE pm_attempts.post_id = p.ID
                     AND pm_attempts.meta_key = '_prorank_optimization_attempts'
                     AND CAST(pm_attempts.meta_value AS UNSIGNED) >= %d
                 )",
                ...array_merge(self::SUPPORTED_MIME_TYPES, [$retry_limit])
            )
        );

        $webp_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
                    AND pm.meta_key = '_prorank_webp_generated'
                    AND pm.meta_value = '1'
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND (p.post_mime_type = %s OR pm.post_id IS NOT NULL)",
                'image/webp'
            )
        );

        $avif_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
                    AND pm.meta_key = '_prorank_avif_generated'
                    AND pm.meta_value = '1'
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND (p.post_mime_type = %s OR pm.post_id IS NOT NULL)",
                'image/avif'
            )
        );

        $jxl_count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT p.ID)
                 FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
                    AND pm.meta_key = '_prorank_jxl_generated'
                    AND pm.meta_value = '1'
                 WHERE p.post_type = 'attachment'
                 AND p.post_status <> 'trash'
                 AND (p.post_mime_type = %s OR pm.post_id IS NOT NULL)",
                'image/jxl'
            )
        );

        $optimizable_total = $source_count + $modern_original_count;

        // Only count images ProRank actually optimized (have _prorank_optimized meta).
        // Modern originals (WebP/AVIF/JXL uploads) are tracked separately so the
        // dashboard doesn't show inflated numbers on a fresh install.
        $stats['optimized'] = $optimized_count;
        $stats['optimized_images'] = $optimized_count;
        $stats['source_images'] = $source_count;
        $stats['already_modern_images'] = $modern_original_count;
        $stats['total'] = $optimizable_total;
        $stats['total_images'] = $optimizable_total;
        $stats['pending'] = $retryable_count;
        $stats['unresolved_images'] = max(0, $source_count - $optimized_count);
        $stats['failed_images'] = $failed_count;
        $stats['exhausted_failed_images'] = $exhausted_failed_count;
        $stats['retryable_failed_images'] = max(0, $failed_count - $exhausted_failed_count);
        $stats['no_gain_images'] = $no_gain_count;
        $stats['bulk_retry_limit'] = $retry_limit;
        $stats['unsupported_images'] = $unsupported_count;
        $stats['all_image_attachments'] = $optimizable_total + $unsupported_count;
        $stats['bytes_saved'] = $current_saved_bytes;
        $stats['total_savings'] = $current_saved_bytes;
        $stats['webp_count'] = $webp_count;
        $stats['avif_count'] = $avif_count;
        $stats['jxl_count'] = $jxl_count;

        // Update cached stats with current values to prevent stale dashboard totals.
        $cached_stats = get_option('prorank_image_optimization_stats', []);
        if (
            !isset($cached_stats['total_images']) ||
            (int) $cached_stats['total_images'] !== $optimizable_total ||
            (int) ($cached_stats['total_savings'] ?? 0) !== $current_saved_bytes ||
            (int) ($cached_stats['bytes_saved'] ?? 0) !== $current_saved_bytes ||
            (int) ($cached_stats['webp_count'] ?? 0) !== $webp_count ||
            (int) ($cached_stats['avif_count'] ?? 0) !== $avif_count ||
            (int) ($cached_stats['jxl_count'] ?? 0) !== $jxl_count
        ) {
            $cached_stats['total_images'] = (int) $optimizable_total;
            $cached_stats['total_savings'] = $current_saved_bytes;
            $cached_stats['bytes_saved'] = $current_saved_bytes;
            $cached_stats['webp_count'] = $webp_count;
            $cached_stats['avif_count'] = $avif_count;
            $cached_stats['jxl_count'] = $jxl_count;
            update_option('prorank_image_optimization_stats', $cached_stats, false);
        }

        $stats['optimization_percentage'] = $stats['total'] > 0
            ? round(($stats['optimized'] / $stats['total']) * 100, 2)
            : 0;

        // Add backup folder size if backups are enabled.
        if ($this->get_setting('backup_originals', true)) {
            $backup_size = 0;
            foreach ($this->get_all_image_backup_directories() as $backup_dir) {
                if (is_dir($backup_dir)) {
                    $backup_size += $this->get_directory_size($backup_dir);
                }
            }

            if ($backup_size > 0) {
                $stats['backup_size'] = $backup_size;
                $stats['backup_size_formatted'] = size_format($backup_size);
            }
        }
        
        return $stats;
    }
    
    /**
     * Convert img tag to picture element with modern image sources
     *
     * @param string $html          Original HTML
     * @param int    $attachment_id Attachment ID
     * @param string $size          Image size
     * @param bool   $icon          Whether it's an icon
     * @param array  $attr          Image attributes
     * @return string Modified HTML
     */
    public function convert_to_picture_element(string $html, int $attachment_id, string $size, bool $icon, array $attr): string {
        if (!$this->is_frontend_optimization_context()) {
            return $html;
        }

        // Skip if delivery is disabled
        if (!$this->get_setting('enable_picture_element', true)) {
            return $html;
        }
        
        // Skip icons
        if ($icon) {
            return $html;
        }
        
        // Skip if no attachment ID
        if (!$attachment_id) {
            return $html;
        }
        
        // Get image metadata
        $metadata = wp_get_attachment_metadata($attachment_id);
        if (!$metadata) {
            return $html;
        }
        
        // Parse the img tag
        $doc = new \DOMDocument();
        @$doc->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        $img = $doc->getElementsByTagName('img')->item(0);
        
        if (!$img) {
            return $html;
        }
        
        // Get original src
        $original_src = $img->getAttribute('src');
        $srcset = $img->getAttribute('srcset');
        $sizes = $img->getAttribute('sizes');
        
        // Check if optimized versions exist
        $has_webp = false;
        $has_avif = false;
        $has_jxl = false;
        $webp_srcset = [];
        $avif_srcset = [];
        $jxl_srcset = [];
        
        // Build srcsets for optimized formats
        if ($srcset) {
            $srcset_parts = explode(',', $srcset);
            foreach ($srcset_parts as $part) {
                $part = trim($part);
                if (preg_match('/(\S+)\s+(\d+w)/', $part, $matches)) {
                    $url = $matches[1];
                    $descriptor = $matches[2];

                    // Generate WebP URL (handles WordPress suffix like -2, -3)
                    $webp_url = $this->get_optimized_url($url, 'webp');
                    $actual_webp_url = $this->get_actual_optimized_url($webp_url);
                    if ($actual_webp_url) {
                        $webp_srcset[] = $actual_webp_url . ' ' . $descriptor;
                        $has_webp = true;
                    }

                    // Generate AVIF URL
                    $avif_url = $this->get_optimized_url($url, 'avif');
                    $actual_avif_url = $this->get_actual_optimized_url($avif_url);
                    if ($actual_avif_url) {
                        $avif_srcset[] = $actual_avif_url . ' ' . $descriptor;
                        $has_avif = true;
                    }

                    // Generate JXL URL
                    $jxl_url = $this->get_optimized_url($url, 'jxl');
                    $actual_jxl_url = $this->get_actual_optimized_url($jxl_url);
                    if ($actual_jxl_url) {
                        $jxl_srcset[] = $actual_jxl_url . ' ' . $descriptor;
                        $has_jxl = true;
                    }
                }
            }
        }

        // Check main image - get actual URLs with WordPress suffixes
        $webp_src = $this->get_optimized_url($original_src, 'webp');
        $actual_webp_src = $this->get_actual_optimized_url($webp_src);
        if ($actual_webp_src) {
            $webp_src = $actual_webp_src;
            $has_webp = true;
        }

        $avif_src = $this->get_optimized_url($original_src, 'avif');
        $actual_avif_src = $this->get_actual_optimized_url($avif_src);
        if ($actual_avif_src) {
            $avif_src = $actual_avif_src;
            $has_avif = true;
        }

        $jxl_src = $this->get_optimized_url($original_src, 'jxl');
        $actual_jxl_src = $this->get_actual_optimized_url($jxl_src);
        if ($actual_jxl_src) {
            $jxl_src = $actual_jxl_src;
            $has_jxl = true;
        }
        
        // If no optimized versions exist, return original
        if (!$has_webp && !$has_avif && !$has_jxl) {
            return $html;
        }
        
        // Create picture element
        $picture = $doc->createElement('picture');
        
        // Add JXL source if available
        if ($has_jxl && $this->get_setting('jxl_enabled', false)) {
            $jxl_source = $doc->createElement('source');
            $jxl_source->setAttribute('type', 'image/jxl');
            $jxl_source->setAttribute('srcset', $jxl_src);

            if (!empty($jxl_srcset)) {
                $jxl_source->setAttribute('srcset', implode(', ', $jxl_srcset));
            }

            if ($sizes) {
                $jxl_source->setAttribute('sizes', $sizes);
            }

            $picture->appendChild($jxl_source);
        }

        // Add AVIF source if available
        if ($has_avif && $this->get_setting('avif_enabled', false)) {
            $avif_source = $doc->createElement('source');
            $avif_source->setAttribute('type', 'image/avif');
            $avif_source->setAttribute('srcset', $avif_src);
            
            if (!empty($avif_srcset)) {
                $avif_source->setAttribute('srcset', implode(', ', $avif_srcset));
            }
            
            if ($sizes) {
                $avif_source->setAttribute('sizes', $sizes);
            }
            
            $picture->appendChild($avif_source);
        }
        
        // Add WebP source if available
        if ($has_webp && $this->get_setting('webp_enabled', false)) {
            $webp_source = $doc->createElement('source');
            $webp_source->setAttribute('type', 'image/webp');
            $webp_source->setAttribute('srcset', $webp_src);
            
            if (!empty($webp_srcset)) {
                $webp_source->setAttribute('srcset', implode(', ', $webp_srcset));
            }
            
            if ($sizes) {
                $webp_source->setAttribute('sizes', $sizes);
            }
            
            $picture->appendChild($webp_source);
        }
        
        // Clone original img as fallback
        $fallback_img = $img->cloneNode(true);
        $picture->appendChild($fallback_img);
        
        // Replace img with picture
        $img->parentNode->replaceChild($picture, $img);
        
        // Get the modified HTML
        $body = $doc->getElementsByTagName('body')->item(0);
        if ($body) {
            $html = $doc->saveHTML($body);
            // Remove body tags
            $html = preg_replace('/<\/?body>/', '', $html);
        } else {
            $html = $doc->saveHTML();
        }
        
        return trim($html);
    }
    
    /**
     * Start output buffering for global picture element conversion
     *
     * @return void
     */
    public function start_output_buffer(): void {
        if (!$this->is_frontend_optimization_context()) {
            return;
        }

        ob_start([$this, 'process_output_buffer']);
    }
    
    /**
     * Process output buffer to convert all images
     *
     * @param string $buffer Output buffer content
     * @return string Modified content
     */
    public function process_output_buffer(string $buffer): string {
        if (!$this->is_frontend_optimization_context()) {
            return $buffer;
        }

        // Skip if not HTML
        if (!preg_match('/<html/i', $buffer)) {
            return $buffer;
        }

        // Skip admin pages
        if (is_admin()) {
            return $buffer;
        }

        if ($this->is_current_request_excluded()) {
            return $buffer;
        }

        // Reset per-response image counter for lazy/eager heuristics.
        $this->buffer_image_counter = 0;
        $buffer = $this->refresh_buffer_inline_images($buffer);

        // Process img tags, but skip those already inside picture elements
        // Use a more sophisticated approach: split by picture elements, process only outside
        return preg_replace_callback(
            '/(<picture[^>]*>.*?<\/picture>)|(<img[^>]+>)/is',
            function($matches) {
                // If it's a picture element, return unchanged
                if (!empty($matches[1])) {
                    return $matches[1];
                }
                // Otherwise process the img tag
                if (!empty($matches[2])) {
                    // Extract attributes from the img tag
                    if (preg_match('/<img([^>]+)>/i', $matches[2], $img_match)) {
                        return $this->process_img_tag($img_match);
                    }
                }
                return $matches[0];
            },
            $buffer
        );
    }

    /**
     * Start output buffer for Accept header based URL replacement
     * This method replaces image URLs directly based on browser support
     * Works for CSS backgrounds, inline styles, and all image URLs
     */
    public function start_accept_header_buffer(): void {
        if (!$this->is_frontend_optimization_context()) {
            return;
        }

        ob_start([$this, 'process_accept_header_buffer']);
    }

    /**
     * Process buffer using Accept header detection
     * Replaces image URLs with WebP/AVIF versions if browser supports them
     *
     * @param string $buffer Output buffer content
     * @return string Modified content
     */
    public function process_accept_header_buffer(string $buffer): string {
        if (!$this->is_frontend_optimization_context()) {
            return $buffer;
        }

        // Skip if not HTML
        if (!preg_match('/<html/i', $buffer)) {
            return $buffer;
        }

        // Skip admin pages
        if (is_admin()) {
            return $buffer;
        }

        if ($this->is_current_request_excluded()) {
            return $buffer;
        }

        $buffer = $this->refresh_buffer_inline_images($buffer);

        // Check browser support via Accept header
        $accepts_jxl = $this->browser_accepts_jxl();
        $accepts_avif = $this->browser_accepts_avif();
        $accepts_webp = $this->browser_accepts_webp();

        // If browser doesn't support modern formats, return unchanged
        if (!$accepts_webp && !$accepts_avif && !$accepts_jxl) {
            return $buffer;
        }

        // Get upload directory info
        $upload_dir = wp_upload_dir();
        $upload_url = preg_quote($upload_dir['baseurl'], '/');

        // Match image URLs in the content (src, srcset, url(), etc.)
        $pattern = '/(' . $upload_url . '\/[^\s"\'\)]+\.(jpe?g|png))/i';

        return preg_replace_callback($pattern, function($matches) use ($accepts_jxl, $accepts_avif, $accepts_webp) {
            $original_url = $matches[1];

            if ($this->matches_exclude_path($original_url)) {
                return $original_url;
            }

            $this->ensure_runtime_optimized_variants($original_url);

            // Prefer JXL if supported and enabled (best compression)
            if ($accepts_jxl && $this->get_setting('jxl_enabled', false)) {
                $jxl_url = $this->get_optimized_url($original_url, 'jxl');
                $actual_jxl = $this->get_actual_optimized_url($jxl_url);
                if ($actual_jxl) {
                    return $actual_jxl;
                }
            }

            // Prefer AVIF if supported and enabled
            if ($accepts_avif && $this->get_setting('avif_enabled', false)) {
                $avif_url = $this->get_optimized_url($original_url, 'avif');
                $actual_avif = $this->get_actual_optimized_url($avif_url);
                if ($actual_avif) {
                    return $actual_avif;
                }
            }

            // Fall back to WebP
            if ($accepts_webp) {
                $webp_url = $this->get_optimized_url($original_url, 'webp');
                $actual_webp = $this->get_actual_optimized_url($webp_url);
                if ($actual_webp) {
                    return $actual_webp;
                }
            }

            // Return original if no optimized version exists
            return $original_url;
        }, $buffer);
    }

    /**
     * Check if browser accepts WebP via Accept header
     *
     * @return bool
     */
    private function browser_accepts_webp(): bool {
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        return strpos($accept, 'image/webp') !== false;
    }

    /**
     * Check if browser accepts AVIF via Accept header
     *
     * @return bool
     */
    private function browser_accepts_avif(): bool {
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        return strpos($accept, 'image/avif') !== false;
    }

    /**
     * Check if browser accepts JXL
     *
     * @return bool
     */
    private function browser_accepts_jxl(): bool {
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        return strpos($accept, 'image/jxl') !== false;
    }

    /**
     * Process individual img tag for picture element conversion
     *
     * @param array $matches Regex matches
     * @return string Modified HTML
     */
    private function process_img_tag(array $matches): string {
        $img_html = $matches[0];
        $attributes = $matches[1];
        $this->buffer_image_counter++;

        // Backfill missing width/height where we can resolve dimensions from local uploads.
        // This helps reduce layout-shift diagnostics on themes/builders that omit intrinsic sizes.
        $img_html = $this->ensure_intrinsic_dimensions($img_html);
        $attributes = $this->extract_img_attributes($img_html);

        // Never rewrite branding/UI assets (logos/icons/avatars) to avoid theme header regressions.
        if ($this->should_skip_runtime_image_rewrite($img_html, $attributes)) {
            return $img_html;
        }

        // Force eager loading on the first likely above-the-fold images.
        if ($this->should_force_eager_loading($img_html, $this->buffer_image_counter)) {
            $img_html = preg_replace('/loading=["\'][^"\']*["\']/i', 'loading="eager"', $img_html, 1);
            $attributes = preg_replace('/loading=["\'][^"\']*["\']/i', 'loading="eager"', $attributes, 1);
        }

        // Avoid conflicting hints: fetchpriority=high should never keep lazy loading.
        // This improves LCP consistency for hero/above-the-fold images across themes/builders.
        if (preg_match('/fetchpriority=["\']high["\']/i', $attributes) && preg_match('/loading=["\']lazy["\']/i', $img_html)) {
            $img_html = preg_replace('/loading=["\']lazy["\']/i', 'loading="eager"', $img_html, 1);
            $attributes = preg_replace('/loading=["\']lazy["\']/i', 'loading="eager"', $attributes, 1);
        }

        // Stabilize replaced image markup to avoid inline baseline/box shifts.
        $img_html = $this->apply_stable_img_defaults($img_html);
        $attributes = $this->extract_img_attributes($img_html);
        
        // Extract src
        if (!preg_match('/src=["\']([^"\']*)["\']/i', $attributes, $src_matches)) {
            return $img_html;
        }
        
        $src = $src_matches[1];
        
        // Skip external images
        $upload_dir = wp_upload_dir();
        if (strpos($src, $upload_dir['baseurl']) === false) {
            return $img_html;
        }

        if ($this->matches_exclude_path($src)) {
            return $img_html;
        }

        $this->ensure_runtime_optimized_variants($src);
        
        // Check if optimized versions exist (with WordPress suffix support)
        $has_webp = false;
        $has_avif = false;
        $has_jxl = false;

        $webp_url = $this->get_optimized_url($src, 'webp');
        $actual_webp_url = $this->get_actual_optimized_url($webp_url);
        if ($actual_webp_url) {
            $webp_url = $actual_webp_url;
            $has_webp = true;
        }

        $avif_url = $this->get_optimized_url($src, 'avif');
        $actual_avif_url = $this->get_actual_optimized_url($avif_url);
        if ($actual_avif_url) {
            $avif_url = $actual_avif_url;
            $has_avif = true;
        }

        $jxl_url = $this->get_optimized_url($src, 'jxl');
        $actual_jxl_url = $this->get_actual_optimized_url($jxl_url);
        if ($actual_jxl_url) {
            $jxl_url = $actual_jxl_url;
            $has_jxl = true;
        }

        // If no optimized versions, return original
        if (!$has_webp && !$has_avif && !$has_jxl) {
            return $img_html;
        }

        // Build picture element — format order: JXL (best compression) > AVIF > WebP > original
        [$width, $height] = $this->extract_dimensions_from_img_tag($img_html);
        // Keep wrapper semantics untouched to avoid alignment/line-box regressions.
        $picture_html = '<picture class="prorank-picture">';

        // Add JXL source (best compression, Chrome 145+ / Firefox support)
        if ($has_jxl && $this->get_setting('jxl_enabled', false)) {
            $jxl_srcset = $jxl_url;
            if (preg_match('/srcset=["\']([^"\']*)["\']/i', $attributes, $srcset_matches)) {
                $responsive_srcset = $this->convert_srcset_to_format($srcset_matches[1], 'jxl');
                if ($responsive_srcset) {
                    $jxl_srcset = $responsive_srcset;
                }
            }

            $picture_html .= '<source type="image/jxl" srcset="' . esc_attr($jxl_srcset) . '"';

            if (preg_match('/sizes=["\']([^"\']*)["\']/i', $attributes, $sizes_matches)) {
                $picture_html .= ' sizes="' . esc_attr($sizes_matches[1]) . '"';
            }

            $picture_html .= '>';
        }

        // Add AVIF source
        if ($has_avif && $this->get_setting('avif_enabled', false)) {
            // Build srcset - prefer responsive srcset if available, otherwise single URL
            $avif_srcset = $avif_url;
            if (preg_match('/srcset=["\']([^"\']*)["\']/i', $attributes, $srcset_matches)) {
                $responsive_srcset = $this->convert_srcset_to_format($srcset_matches[1], 'avif');
                if ($responsive_srcset) {
                    $avif_srcset = $responsive_srcset;
                }
            }

            $picture_html .= '<source type="image/avif" srcset="' . esc_attr($avif_srcset) . '"';

            // Copy sizes if exists
            if (preg_match('/sizes=["\']([^"\']*)["\']/i', $attributes, $sizes_matches)) {
                $picture_html .= ' sizes="' . esc_attr($sizes_matches[1]) . '"';
            }

            $picture_html .= '>';
        }
        
        // Add WebP source
        if ($has_webp && $this->get_setting('webp_enabled', false)) {
            // Build srcset - prefer responsive srcset if available, otherwise single URL
            $webp_srcset = $webp_url;
            if (preg_match('/srcset=["\']([^"\']*)["\']/i', $attributes, $srcset_matches)) {
                $responsive_srcset = $this->convert_srcset_to_format($srcset_matches[1], 'webp');
                if ($responsive_srcset) {
                    $webp_srcset = $responsive_srcset;
                }
            }

            $picture_html .= '<source type="image/webp" srcset="' . esc_attr($webp_srcset) . '"';

            // Copy sizes if exists
            if (preg_match('/sizes=["\']([^"\']*)["\']/i', $attributes, $sizes_matches)) {
                $picture_html .= ' sizes="' . esc_attr($sizes_matches[1]) . '"';
            }

            $picture_html .= '>';
        }
        
        // Add original img as fallback
        $picture_html .= $img_html;
        $picture_html .= '</picture>';
        
        return $picture_html;
    }

    /**
     * Decide whether an image should be forced eager for early paint stability.
     *
     * @param string $img_html   Raw image HTML.
     * @param int    $index      1-based image index in buffered HTML.
     * @return bool
     */
    private function should_force_eager_loading(string $img_html, int $index): bool {
        unset($index); // Kept for backward-compatible signature.

        // Only force eager when another optimizer has already identified this
        // image as the LCP candidate via fetchpriority=high.
        if (!preg_match('/fetchpriority=["\']high["\']/i', $img_html)) {
            return false;
        }

        return preg_match('/loading=["\']lazy["\']/i', $img_html) === 1;
    }

    /**
     * Determine if runtime image rewriting should be skipped.
     *
     * @param string $img_html Full img tag HTML.
     * @param string $attributes Inner img attributes.
     * @return bool
     */
    private function should_skip_runtime_image_rewrite(string $img_html, string $attributes): bool {
        $attrs_lower = strtolower($attributes);

        // Theme/builder explicit opt-out hooks.
        foreach ([
            'data-no-optimize',
            'data-no-optimization',
            'data-skip-optimize',
            'data-prorank-skip-image',
            'data-skip-lazy',
            'data-no-lazyload',
        ] as $optout_attr) {
            if (strpos($attrs_lower, $optout_attr) !== false) {
                return true;
            }
        }

        $class_value = strtolower($this->extract_attribute_value($attributes, 'class'));
        foreach ([
            'logo',
            'custom-logo',
            'site-logo',
            'brand-logo',
            'header-logo',
            'site-icon',
            'avatar',
            'emoji',
            'smiley',
            'spinner',
            'loader',
        ] as $needle) {
            if ($class_value !== '' && strpos($class_value, $needle) !== false) {
                return true;
            }
        }

        $variant = strtolower($this->extract_attribute_value($attributes, 'data-variant'));
        if (in_array($variant, ['logo', 'icon', 'avatar'], true)) {
            return true;
        }

        // Heuristic fallback for unclassed branding assets.
        foreach (['alt', 'title', 'src'] as $attr) {
            $value = strtolower($this->extract_attribute_value($attributes, $attr));
            if ($value === '') {
                continue;
            }
            if (preg_match('/(^|[^a-z0-9])(logo|favicon|site-icon|avatar)([^a-z0-9]|$)/', $value)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Ensure converted <img> tags have stable rendering defaults.
     *
     * @param string $img_html Raw image HTML.
     * @return string
     */
    private function apply_stable_img_defaults(string $img_html): string {
        [$width, $height] = $this->extract_dimensions_from_img_tag($img_html);
        if ($width <= 0 || $height <= 0) {
            return $img_html;
        }

        $aspect_ratio = 'aspect-ratio:' . $width . '/' . $height;

        if (preg_match('/style\s*=\s*["\']([^"\']*)["\']/i', $img_html, $style_match)) {
            $current = trim((string) ($style_match[1] ?? ''));
            if (stripos($current, 'aspect-ratio:') !== false) {
                return $img_html;
            }

            $current = rtrim($current, '; ') . ';' . $aspect_ratio . ';';
            return str_replace($style_match[0], 'style="' . $current . '"', $img_html);
        }

        // Keep original display/alignment semantics; only add ratio reservation.
        return str_replace('<img', '<img style="' . $aspect_ratio . ';"', $img_html);
    }

    /**
     * Extract inner attribute segment from a full img tag.
     *
     * @param string $img_html
     * @return string
     */
    private function extract_img_attributes(string $img_html): string {
        if (preg_match('/<img([^>]+)>/i', $img_html, $m)) {
            return (string) ($m[1] ?? '');
        }
        return '';
    }

    /**
     * Extract a single attribute value from an img attribute string.
     *
     * @param string $attributes Img attribute segment.
     * @param string $attribute Attribute name.
     * @return string
     */
    private function extract_attribute_value(string $attributes, string $attribute): string {
        $quoted = '/\b' . preg_quote($attribute, '/') . '\s*=\s*["\']([^"\']*)["\']/i';
        if (preg_match($quoted, $attributes, $match)) {
            return (string) ($match[1] ?? '');
        }

        $unquoted = '/\b' . preg_quote($attribute, '/') . '\s*=\s*([^\s>]+)/i';
        if (preg_match($unquoted, $attributes, $match)) {
            return (string) ($match[1] ?? '');
        }

        return '';
    }

    /**
     * Extract width/height from an img tag.
     *
     * @param string $img_html
     * @return array{0:int,1:int}
     */
    private function extract_dimensions_from_img_tag(string $img_html): array {
        $width = 0;
        $height = 0;
        if (preg_match('/\bwidth\s*=\s*["\']?(\d+)/i', $img_html, $w_match)) {
            $width = (int) ($w_match[1] ?? 0);
        }
        if (preg_match('/\bheight\s*=\s*["\']?(\d+)/i', $img_html, $h_match)) {
            $height = (int) ($h_match[1] ?? 0);
        }

        return [$width, $height];
    }

    /**
     * Ensure an image tag includes intrinsic width/height attributes when resolvable.
     *
     * @param string $img_html
     * @return string
     */
    private function ensure_intrinsic_dimensions(string $img_html): string {
        [$width, $height] = $this->extract_dimensions_from_img_tag($img_html);
        if ($width > 0 && $height > 0) {
            return $this->mark_intrinsic_dimension_html($img_html, $width, $height);
        }

        if ($this->should_skip_intrinsic_dimensions_for_markup($img_html)) {
            return $img_html;
        }

        $src = $this->extract_attribute_value($this->extract_img_attributes($img_html), 'src');
        if ($src === '' || stripos($src, 'data:') === 0) {
            return $img_html;
        }

        [$resolved_width, $resolved_height] = $this->resolve_dimensions_from_src($src);
        if ($resolved_width <= 0 || $resolved_height <= 0) {
            return $img_html;
        }

        // Insert attributes right after <img for consistency.
        $rewritten = preg_replace(
            '/<img\b/i',
            sprintf('<img width="%d" height="%d"', $resolved_width, $resolved_height),
            $img_html,
            1
        ) ?: $img_html;

        return $this->mark_intrinsic_dimension_html($rewritten, $resolved_width, $resolved_height);
    }

    /**
     * Determine whether this image markup should keep builder-managed sizing.
     *
     * @param string $img_html
     * @return bool
     */
    private function should_skip_intrinsic_dimensions_for_markup(string $img_html): bool {
        $haystack = strtolower($img_html);
        foreach (PerformanceCompatibilityRegistry::get_image_dimension_skip_markers() as $marker) {
            if ($marker !== '' && strpos($haystack, $marker) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Resolve image dimensions from URL or filename pattern.
     *
     * @param string $src
     * @return array{0:int,1:int}
     */
    private function resolve_dimensions_from_src(string $src): array {
        $cache_key = trim((string) $src);
        if ($cache_key === '') {
            return [0, 0];
        }
        if (isset($this->dimension_lookup_cache[$cache_key])) {
            return $this->dimension_lookup_cache[$cache_key];
        }

        $clean_src = preg_replace('/[#?].*$/', '', $cache_key) ?: $cache_key;

        // Fast path: WordPress sub-size filenames usually include dimensions, e.g. -768x512.
        if (preg_match('/-(\d{1,5})x(\d{1,5})\.(?:avif|webp|jxl|jpe?g|png|gif)$/i', $clean_src, $m)) {
            $result = [(int) ($m[1] ?? 0), (int) ($m[2] ?? 0)];
            $this->dimension_lookup_cache[$cache_key] = $result;
            return $result;
        }

        $path = $this->map_upload_url_to_path($clean_src);
        if ($path === '' || !file_exists($path)) {
            $this->dimension_lookup_cache[$cache_key] = [0, 0];
            return [0, 0];
        }

        if (preg_match('/\.svg$/i', $path)) {
            $svg_dimensions = $this->resolve_svg_dimensions($path);
            $this->dimension_lookup_cache[$cache_key] = $svg_dimensions;
            return $svg_dimensions;
        }

        if (!function_exists('wp_getimagesize')) {
            $this->dimension_lookup_cache[$cache_key] = [0, 0];
            return [0, 0];
        }

        $size = wp_getimagesize($path);
        if (!is_array($size) || empty($size[0]) || empty($size[1])) {
            $this->dimension_lookup_cache[$cache_key] = [0, 0];
            return [0, 0];
        }

        $result = [(int) $size[0], (int) $size[1]];
        $this->dimension_lookup_cache[$cache_key] = $result;
        return $result;
    }

    /**
     * Resolve width/height from a local SVG file.
     *
     * @param string $path
     * @return array{0:int,1:int}
     */
    private function resolve_svg_dimensions(string $path): array {
        $svg = @file_get_contents($path, false, null, 0, 8192);
        if (!is_string($svg) || $svg === '') {
            return [0, 0];
        }

        $width = $this->extract_svg_length($svg, 'width');
        $height = $this->extract_svg_length($svg, 'height');
        if ($width > 0 && $height > 0) {
            return [$width, $height];
        }

        if (preg_match('/viewBox\s*=\s*["\']\s*[-\d.]+\s+[-\d.]+\s+([-\d.]+)\s+([-\d.]+)\s*["\']/i', $svg, $matches)) {
            $viewbox_width = (int) round((float) ($matches[1] ?? 0));
            $viewbox_height = (int) round((float) ($matches[2] ?? 0));
            if ($viewbox_width > 0 && $viewbox_height > 0) {
                return [$viewbox_width, $viewbox_height];
            }
        }

        return [0, 0];
    }

    /**
     * Extract a numeric SVG length attribute, ignoring common CSS units.
     *
     * @param string $svg
     * @param string $attribute
     * @return int
     */
    private function extract_svg_length(string $svg, string $attribute): int {
        if (!preg_match('/\b' . preg_quote($attribute, '/') . '\s*=\s*["\']\s*([0-9]+(?:\.[0-9]+)?)(?:px)?\s*["\']/i', $svg, $matches)) {
            return 0;
        }

        return (int) round((float) ($matches[1] ?? 0));
    }

    /**
     * Persist intrinsic dimensions as data attributes so client-side widgets
     * can restore them after lazy/hydration mutations.
     *
     * @param string $img_html
     * @param int    $width
     * @param int    $height
     * @return string
     */
    private function mark_intrinsic_dimension_html(string $img_html, int $width, int $height): string {
        if ($width <= 0 || $height <= 0) {
            return $img_html;
        }

        $replacements = [];
        if (!preg_match('/\bdata-prorank-width\s*=/i', $img_html)) {
            $replacements[] = sprintf('data-prorank-width="%d"', $width);
        }
        if (!preg_match('/\bdata-prorank-height\s*=/i', $img_html)) {
            $replacements[] = sprintf('data-prorank-height="%d"', $height);
        }

        if (empty($replacements)) {
            return $img_html;
        }

        return preg_replace(
            '/<img\b/i',
            '<img ' . implode(' ', $replacements),
            $img_html,
            1
        ) ?: $img_html;
    }

    /**
     * Attach intrinsic-dimension data attributes to attachment image attrs.
     *
     * @param array<string,mixed> $attr
     * @return array<string,mixed>
     */
    private function append_intrinsic_dimension_data_attrs(array $attr): array {
        $width = isset($attr['width']) ? (int) $attr['width'] : 0;
        $height = isset($attr['height']) ? (int) $attr['height'] : 0;

        if ($width > 0 && $height > 0) {
            $attr['data-prorank-width'] = (string) $width;
            $attr['data-prorank-height'] = (string) $height;
        }

        return $attr;
    }

    /**
     * Map an uploads URL to a local filesystem path.
     *
     * @param string $url
     * @return string
     */
    private function map_upload_url_to_path(string $url): string {
        $upload_dir = wp_upload_dir();
        $baseurl = isset($upload_dir['baseurl']) ? (string) $upload_dir['baseurl'] : '';
        $basedir = isset($upload_dir['basedir']) ? (string) $upload_dir['basedir'] : '';

        if ($baseurl !== '' && $basedir !== '' && strpos($url, $baseurl) === 0) {
            $suffix = ltrim(substr($url, strlen($baseurl)), '/');
            return trailingslashit($basedir) . $suffix;
        }

        $parsed = wp_parse_url($url);
        $path = is_array($parsed) ? (string) ($parsed['path'] ?? '') : '';
        if ($path === '') {
            return '';
        }

        $uploads_pos = strpos($path, '/wp-content/uploads/');
        if ($uploads_pos === false) {
            return '';
        }

        $relative = ltrim(substr($path, $uploads_pos + strlen('/wp-content/uploads/')), '/');
        if ($relative === '') {
            return '';
        }

        if ($basedir !== '') {
            return trailingslashit($basedir) . $relative;
        }

        return '';
    }
    
    /**
     * Convert srcset to optimized format
     *
     * @param string $srcset Original srcset
     * @param string $format Target format (webp or avif)
     * @return string|null Converted srcset or null if no optimized versions
     */
    private function convert_srcset_to_format(string $srcset, string $format): ?string {
        $converted_parts = [];
        $parts = explode(',', $srcset);

        foreach ($parts as $part) {
            $part = trim($part);
            if (preg_match('/(\S+)\s+(\d+w|\d+x)/', $part, $matches)) {
                $url = $matches[1];
                $descriptor = $matches[2];

                if ($this->matches_exclude_path($url)) {
                    continue;
                }

                $this->ensure_runtime_optimized_variants($url);

                $optimized_url = $this->get_optimized_url($url, $format);
                $actual_url = $this->get_actual_optimized_url($optimized_url);
                if ($actual_url) {
                    $converted_parts[] = $actual_url . ' ' . $descriptor;
                }
            }
        }

        return !empty($converted_parts) ? implode(', ', $converted_parts) : null;
    }
    
    /**
     * Setup .htaccess rules for serving optimized images
     *
     * @return void
     */
    public function setup_htaccess_rules(): void {
        // Check if server supports it
        if (!$this->server_supports_htaccess_delivery()) {
            return;
        }
        
        $htaccess_file = get_home_path() . '.htaccess';
        if (!wp_is_writable($htaccess_file)) {
            return;
        }
        
        $rules = $this->generate_htaccess_rules();
        insert_with_markers($htaccess_file, 'ProRank Image Optimization', $rules);
    }
    
    /**
     * Generate .htaccess rules for image delivery
     *
     * @return array Array of rules
     */
    private function generate_htaccess_rules(): array {
        $rules = [];

        $rules[] = '<IfModule mod_mime.c>';
        $rules[] = 'AddType image/avif .avif';
        $rules[] = 'AddType image/webp .webp';
        $rules[] = 'AddType image/jxl .jxl';
        $rules[] = '</IfModule>';
        $rules[] = '';
        
        $rules[] = '<IfModule mod_rewrite.c>';
        $rules[] = 'RewriteEngine On';

        // JXL rules (best compression, Chrome 145+ / Firefox)
        if ($this->get_setting('jxl_enabled', false)) {
            $rules[] = '';
            $rules[] = '# Serve JPEG XL images if available';
            $rules[] = 'RewriteCond %{HTTP_ACCEPT} image/jxl';
            $rules[] = 'RewriteCond %{REQUEST_FILENAME} ^(.+)\.(jpe?g|png)$ [NC]';
            $rules[] = 'RewriteCond %1.jxl -f';
            $rules[] = 'RewriteRule ^(.+)\.(jpe?g|png)$ $1.jxl [T=image/jxl,L]';
        }

        // AVIF rules
        if ($this->get_setting('avif_enabled', false)) {
            $rules[] = '';
            $rules[] = '# Serve AVIF images if available';
            $rules[] = 'RewriteCond %{HTTP_ACCEPT} image/avif';
            $rules[] = 'RewriteCond %{REQUEST_FILENAME} ^(.+)\.(jpe?g|png)$ [NC]';
            $rules[] = 'RewriteCond %1.avif -f';
            $rules[] = 'RewriteRule ^(.+)\.(jpe?g|png)$ $1.avif [T=image/avif,L]';
        }
        
        // WebP rules
        if ($this->get_setting('webp_enabled', false)) {
            $rules[] = '';
            $rules[] = '# Serve WebP images if available';
            $rules[] = 'RewriteCond %{HTTP_ACCEPT} image/webp';
            $rules[] = 'RewriteCond %{REQUEST_FILENAME} ^(.+)\.(jpe?g|png)$ [NC]';
            $rules[] = 'RewriteCond %1.webp -f';
            $rules[] = 'RewriteRule ^(.+)\.(jpe?g|png)$ $1.webp [T=image/webp,L]';
        }
        
        $rules[] = '</IfModule>';
        
        return $rules;
    }
    
    /**
     * Check if server supports .htaccess delivery
     *
     * @return bool
     */
    private function server_supports_htaccess_delivery(): bool {
        // Apache and LiteSpeed both honor .htaccess rewrite rules.
        $server_software = \prorank_get_server_var( 'SERVER_SOFTWARE' );
        if (
            $server_software === ''
            || (
                stripos($server_software, 'apache') === false
                && stripos($server_software, 'litespeed') === false
            )
        ) {
            return false;
        }
        
        // Check if mod_rewrite is loaded
        if (!function_exists('apache_get_modules')) {
            return true; // Assume it's available if we can't check
        }
        
        $modules = apache_get_modules();
        return in_array('mod_rewrite', $modules, true);
    }
    
    /**
     * Add rewrite rules for serving optimized images
     *
     * @return void
     */
    public function add_rewrite_rules(): void {
        // Add endpoint for checking optimized image availability
        add_rewrite_endpoint('prorank-image-check', EP_ROOT);
    }
    
    /**
     * Process single image optimization via Action Scheduler
     *
     * @param int $attachment_id Attachment ID to optimize
     * @return void
     */
    public function process_single_image_optimization(int $attachment_id): void {
        $file_path = get_attached_file($attachment_id);
        $current_image = $file_path ? basename($file_path) : ('ID ' . $attachment_id);
        $attempts = (int) get_post_meta($attachment_id, '_prorank_optimization_attempts', true);
        update_post_meta($attachment_id, '_prorank_optimization_attempts', (string) ($attempts + 1));
        $metadata = wp_get_attachment_metadata($attachment_id);

        if (!$metadata && $file_path && file_exists($file_path)) {
            if (!function_exists('wp_generate_attachment_metadata')) {
                require_once ABSPATH . 'wp-admin/includes/image.php';
            }

            $generated_metadata = wp_generate_attachment_metadata($attachment_id, $file_path);
            if (is_array($generated_metadata) && !empty($generated_metadata)) {
                wp_update_attachment_metadata($attachment_id, $generated_metadata);
                $metadata = $generated_metadata;
            }
        }

        if (!$metadata) {
            update_post_meta($attachment_id, '_prorank_optimization_failed', '1');
            update_post_meta($attachment_id, '_prorank_optimization_failure_reason', 'missing_attachment_metadata');
            $this->record_bulk_job_failure($current_image, 'missing_attachment_metadata', $attachment_id);
            return;
        }

        delete_post_meta($attachment_id, '_prorank_optimization_failed');
        delete_post_meta($attachment_id, '_prorank_optimization_failure_reason');
        $this->clear_attachment_no_gain_state($attachment_id);

        $progress = get_transient('prorank_image_optimization_progress');

        // Ensure base structure
        if (!is_array($progress)) {
            $progress = [
                'current' => 0,
                'total' => 0,
                'bytes_saved' => 0,
                'log' => [],
            ];
        }

        // Mark current image
        $progress['current_image'] = $current_image;
        $progress['active_index'] = ($progress['current'] ?? 0) + 1;
        $progress['log'][] = [
            'image' => $current_image,
            'status' => 'processing',
            'ts' => time(),
        ];
        if (count($progress['log']) > 50) {
            $progress['log'] = array_slice($progress['log'], -50);
        }
        $progress['updated_at'] = time();
        set_transient('prorank_image_optimization_progress', $progress, HOUR_IN_SECONDS);

        // Update bulk job progress (if a bulk job is running)
        $this->record_bulk_job_progress($current_image, 0, false);

        // Check memory availability before processing
        $file_info = wp_getimagesize($file_path);
        if ($file_info) {
            $required_memory = ($file_info[0] ?? 0) * ($file_info[1] ?? 0) * 4 * 2; // 2 copies in GD
            $available = $this->get_available_memory();
            if ($available > 0 && $required_memory > $available) {
                update_post_meta($attachment_id, '_prorank_optimization_failed', '1');
                update_post_meta($attachment_id, '_prorank_optimization_failure_reason', 'insufficient_memory');
                $this->record_bulk_job_failure($current_image, 'insufficient_memory', $attachment_id);
                return;
            }
        }

        try {
            $this->generate_optimized_images($metadata, $attachment_id);
        } catch (\Throwable $e) {
            update_post_meta($attachment_id, '_prorank_optimization_failed', '1');
            update_post_meta($attachment_id, '_prorank_optimization_failure_reason', $e->getMessage());
            $this->record_bulk_job_failure($current_image, $e->getMessage(), $attachment_id);
            return;
        }

        $failure_reason = (string) get_post_meta($attachment_id, '_prorank_optimization_failure_reason', true);
        if ((string) get_post_meta($attachment_id, '_prorank_optimization_failed', true) === '1' && $failure_reason !== '') {
            $this->record_bulk_job_failure($current_image, $failure_reason, $attachment_id);
            return;
        }

        if ($this->is_attachment_no_gain($attachment_id)) {
            $this->record_bulk_job_skipped(
                $current_image,
                $this->get_attachment_no_gain_reason($attachment_id),
                $attachment_id
            );
            return;
        }

        // Update progress after processing
        $saved_bytes = (int) get_post_meta($attachment_id, '_prorank_bytes_saved', true);
        $progress['current'] = isset($progress['current']) ? ((int) $progress['current'] + 1) : 1;
        $progress['bytes_saved'] = ($progress['bytes_saved'] ?? 0) + $saved_bytes;
        $progress['current_image'] = '';
        $progress['active_index'] = null;
        $progress['log'][] = [
            'image' => $current_image,
            'status' => 'done',
            'bytes_saved' => $saved_bytes,
            'ts' => time(),
        ];
        if (count($progress['log']) > 50) {
            $progress['log'] = array_slice($progress['log'], -50);
        }
        // Summary aggregation
        if (!isset($progress['summary']) || !is_array($progress['summary'])) {
            $progress['summary'] = ['processed' => 0, 'saved_bytes' => 0];
        }
        $progress['summary']['processed'] = ($progress['summary']['processed'] ?? 0) + 1;
        $progress['summary']['saved_bytes'] = ($progress['summary']['saved_bytes'] ?? 0) + $saved_bytes;

        $progress['completed'] = isset($progress['total'], $progress['current']) && $progress['total'] > 0
            ? $progress['current'] >= $progress['total']
            : false;
        if (!empty($progress['total'])) {
            $progress['percent'] = round(($progress['current'] / $progress['total']) * 100, 2);
        }

        $progress['updated_at'] = time();
        set_transient('prorank_image_optimization_progress', $progress, HOUR_IN_SECONDS);

        // Update bulk job progress with completion (if a bulk job is running)
        $this->record_bulk_job_progress($current_image, $saved_bytes, true);

        if (!empty($progress['completed'])) {
            delete_transient('prorank_image_conversion_running');
        }
    }

    /**
     * Record a failed image in bulk progress so a bad attachment cannot block the queue forever.
     *
     * @param string $current_image Current image label.
     * @param string $reason Failure reason slug.
     * @param int    $attachment_id Attachment ID.
     * @return void
     */
    private function record_bulk_job_failure(string $current_image, string $reason, int $attachment_id = 0): void {
        $progress = get_transient('prorank_image_optimization_progress');
        if (!is_array($progress)) {
            $progress = [
                'current' => 0,
                'total' => 0,
                'bytes_saved' => 0,
                'log' => [],
            ];
        }

        $progress['current'] = isset($progress['current']) ? ((int) $progress['current'] + 1) : 1;
        $progress['current_image'] = '';
        $progress['active_index'] = null;
        $progress['log'][] = [
            'image' => $current_image,
            'status' => 'failed',
            'reason' => $reason,
            'ts' => time(),
        ];
        if (count($progress['log']) > 50) {
            $progress['log'] = array_slice($progress['log'], -50);
        }

        if (!isset($progress['summary']) || !is_array($progress['summary'])) {
            $progress['summary'] = ['processed' => 0, 'saved_bytes' => 0, 'failed' => 0];
        }
        $progress['summary']['processed'] = ($progress['summary']['processed'] ?? 0) + 1;
        $progress['summary']['failed'] = ($progress['summary']['failed'] ?? 0) + 1;

        $progress['completed'] = isset($progress['total'], $progress['current']) && $progress['total'] > 0
            ? $progress['current'] >= $progress['total']
            : false;
        if (!empty($progress['total'])) {
            $progress['percent'] = round(($progress['current'] / $progress['total']) * 100, 2);
        }

        $progress['updated_at'] = time();
        set_transient('prorank_image_optimization_progress', $progress, HOUR_IN_SECONDS);

        $job_id = (string) get_option('prorank_bulk_job_active', '');
        if ($job_id !== '') {
            $job_data = get_option('prorank_bulk_job_' . $job_id, []);
            $job_status = is_array($job_data) ? (string) ($job_data['status'] ?? '') : '';
            if (is_array($job_data) && !empty($job_data) && !in_array($job_status, ['completed', 'stopped', 'failed'], true)) {
                $log = $job_data['log'] ?? [];
                $log[] = [
                    'image' => $current_image,
                    'status' => 'failed',
                    'reason' => $reason,
                    'attachment_id' => $attachment_id,
                    'ts' => time(),
                ];
                if (count($log) > 50) {
                    $log = array_slice($log, -50);
                }

                $processed = (int) ($job_data['processed'] ?? 0) + 1;
                $failed = (int) ($job_data['failed'] ?? 0) + 1;
                $failed_images = $job_data['failed_images'] ?? [];
                $failed_images[] = [
                    'image' => $current_image,
                    'reason' => $reason,
                    'attachment_id' => $attachment_id,
                    'ts' => time(),
                ];
                if (count($failed_images) > 50) {
                    $failed_images = array_slice($failed_images, -50);
                }

                $update = [
                    'current_image' => '',
                    'processed' => $processed,
                    'failed' => $failed,
                    'failed_images' => $failed_images,
                    'log' => $log,
                    'status' => 'running',
                ];

                $total = (int) ($job_data['total'] ?? 0);
                if ($total > 0 && $processed >= $total) {
                    $update['status'] = 'completed';
                    $update['completed_at'] = time();
                    update_option('prorank_bulk_job_last', $job_id, false);
                    delete_option('prorank_bulk_job_active');
                }

                $controller = new ImageOptimizationController();
                $controller->record_bulk_job_update($job_id, $update);
            }
        }

        if (!empty($progress['completed'])) {
            delete_transient('prorank_image_conversion_running');
        }
    }

    /**
     * Record a terminal no-gain result so bulk progress can advance without treating the image as failed.
     *
     * @param string $current_image Current image label.
     * @param string $reason Skip reason slug.
     * @param int    $attachment_id Attachment ID.
     * @return void
     */
    private function record_bulk_job_skipped(string $current_image, string $reason, int $attachment_id = 0): void {
        $progress = get_transient('prorank_image_optimization_progress');
        if (!is_array($progress)) {
            $progress = [
                'current' => 0,
                'total' => 0,
                'bytes_saved' => 0,
                'log' => [],
            ];
        }

        $progress['current'] = isset($progress['current']) ? ((int) $progress['current'] + 1) : 1;
        $progress['current_image'] = '';
        $progress['active_index'] = null;
        $progress['log'][] = [
            'image' => $current_image,
            'status' => 'skipped',
            'reason' => $reason !== '' ? $reason : 'no_effective_output',
            'ts' => time(),
        ];
        if (count($progress['log']) > 50) {
            $progress['log'] = array_slice($progress['log'], -50);
        }

        if (!isset($progress['summary']) || !is_array($progress['summary'])) {
            $progress['summary'] = ['processed' => 0, 'saved_bytes' => 0, 'failed' => 0, 'skipped' => 0];
        }
        $progress['summary']['processed'] = ($progress['summary']['processed'] ?? 0) + 1;
        $progress['summary']['skipped'] = ($progress['summary']['skipped'] ?? 0) + 1;

        $progress['completed'] = isset($progress['total'], $progress['current']) && $progress['total'] > 0
            ? $progress['current'] >= $progress['total']
            : false;
        if (!empty($progress['total'])) {
            $progress['percent'] = round(($progress['current'] / $progress['total']) * 100, 2);
        }

        $progress['updated_at'] = time();
        set_transient('prorank_image_optimization_progress', $progress, HOUR_IN_SECONDS);

        $job_id = (string) get_option('prorank_bulk_job_active', '');
        if ($job_id !== '') {
            $job_data = get_option('prorank_bulk_job_' . $job_id, []);
            $job_status = is_array($job_data) ? (string) ($job_data['status'] ?? '') : '';
            if (is_array($job_data) && !empty($job_data) && !in_array($job_status, ['completed', 'stopped', 'failed'], true)) {
                $log = $job_data['log'] ?? [];
                $log[] = [
                    'image' => $current_image,
                    'status' => 'skipped',
                    'reason' => $reason !== '' ? $reason : 'no_effective_output',
                    'attachment_id' => $attachment_id,
                    'ts' => time(),
                ];
                if (count($log) > 50) {
                    $log = array_slice($log, -50);
                }

                $processed = (int) ($job_data['processed'] ?? 0) + 1;
                $skipped = (int) ($job_data['skipped'] ?? 0) + 1;

                $update = [
                    'current_image' => '',
                    'processed' => $processed,
                    'skipped' => $skipped,
                    'log' => $log,
                    'status' => 'running',
                ];

                $total = (int) ($job_data['total'] ?? 0);
                if ($total > 0 && $processed >= $total) {
                    $update['status'] = 'completed';
                    $update['completed_at'] = time();
                    update_option('prorank_bulk_job_last', $job_id, false);
                    delete_option('prorank_bulk_job_active');
                }

                $controller = new ImageOptimizationController();
                $controller->record_bulk_job_update($job_id, $update);
            }
        }

        if (!empty($progress['completed'])) {
            delete_transient('prorank_image_conversion_running');
        }
    }

    /**
     * Handle bulk optimization continuation action.
     *
     * @param mixed $job_id Job ID or array with job_id/next_offset.
     * @param mixed $next_offset Next offset.
     * @return void
     */
    public function handle_bulk_optimization_continue($job_id = '', $next_offset = 0): void {
        if (is_array($job_id)) {
            $next_offset = $job_id['next_offset'] ?? 0;
            $job_id = $job_id['job_id'] ?? '';
        }

        $job_id = is_scalar($job_id) ? (string) $job_id : '';
        $next_offset = is_numeric($next_offset) ? (int) $next_offset : 0;

        if ($job_id === '') {
            return;
        }

        $controller = new ImageOptimizationController();
        $controller->handle_bulk_optimization_continue($job_id, $next_offset);
    }

    /**
     * Record progress for active bulk job and sync UI data.
     *
     * @param string $current_image Current image label.
     * @param int    $saved_bytes Bytes saved for this image.
     * @param bool   $completed Whether this image finished processing.
     * @return void
     */
    private function record_bulk_job_progress(string $current_image, int $saved_bytes, bool $completed): void {
        $job_id = (string) get_option('prorank_bulk_job_active', '');
        if ($job_id === '') {
            return;
        }

        $job_data = get_option('prorank_bulk_job_' . $job_id, []);
        if (!is_array($job_data) || empty($job_data)) {
            return;
        }

        if (!empty($job_data['status']) && in_array($job_data['status'], ['completed', 'stopped', 'failed'], true)) {
            return;
        }

        $log = $job_data['log'] ?? [];
        $log[] = [
            'image' => $current_image,
            'status' => $completed ? 'done' : 'processing',
            'bytes_saved' => $saved_bytes,
            'ts' => time(),
        ];
        if (count($log) > 50) {
            $log = array_slice($log, -50);
        }

        $update = [
            'current_image' => $completed ? '' : $current_image,
            'log' => $log,
            'status' => 'running',
        ];

        if ($completed) {
            $processed = (int) ($job_data['processed'] ?? 0) + 1;
            $bytes_total = (int) ($job_data['bytes_saved'] ?? 0) + $saved_bytes;
            $update['processed'] = $processed;
            $update['bytes_saved'] = $bytes_total;

            $total = (int) ($job_data['total'] ?? 0);
            if ($total > 0 && $processed >= $total) {
                $update['status'] = 'completed';
                $update['completed_at'] = time();
                update_option('prorank_bulk_job_last', $job_id, false);
                delete_option('prorank_bulk_job_active');
            }
        }

        $controller = new ImageOptimizationController();
        $controller->record_bulk_job_update($job_id, $update);
    }
    
    /**
     * Queue failed optimization for later retry
     *
     * @param string $file_path Source file path
     * @param string $format Target format
     * @param int $quality Quality setting
     * @param string $error_message Error message
     * @return void
     */
    private function queue_failed_optimization(string $file_path, string $format, int $quality, string $error_message): void {
        $queue = get_option('prorank_failed_optimizations', []);
        
        // Add to queue with metadata
        $queue[] = [
            'file_path' => $file_path,
            'format' => $format,
            'quality' => $quality,
            'error' => $error_message,
            'attempts' => 1,
            'queued_at' => current_time('mysql'),
            'last_attempt' => current_time('mysql'),
        ];
        
        // Keep only last 100 failed items
        if (count($queue) > 100) {
            $queue = array_slice($queue, -100);
        }
        
        update_option('prorank_failed_optimizations', $queue, false);
        
        // Schedule retry job if not already scheduled
        if (!wp_next_scheduled('prorank_retry_failed_optimizations')) {
            wp_schedule_single_event(time() + 3600, 'prorank_retry_failed_optimizations');
        }
    }
    
    /**
     * Process failed optimization queue
     *
     * @return void
     */
    public function process_failed_optimization_queue(): void {
        $queue = get_option('prorank_failed_optimizations', []);
        
        if (empty($queue)) {
            return;
        }
        
        $processed = [];
        $remaining = [];
        
        foreach ($queue as $item) {
            // Skip if too many attempts
            if ($item['attempts'] >= 5) {
                continue;
            }
            
            // Skip if tried recently (within 1 hour)
            $last_attempt = strtotime($item['last_attempt']);
            if ((time() - $last_attempt) < 3600) {
                $remaining[] = $item;
                continue;
            }
            
            // Try to optimize again
            $success = $this->optimize_via_cloud(
                $item['file_path'],
                $this->get_optimized_path($item['file_path'], $item['format']),
                $item['format'],
                $item['quality']
            );
            
            if (!$success) {
                // Increment attempts and re-queue
                $item['attempts']++;
                $item['last_attempt'] = current_time('mysql');
                $remaining[] = $item;
            } else {
                $processed[] = $item['file_path'];
            }
        }
        
        // Update queue
        update_option('prorank_failed_optimizations', $remaining, false);
        
        // Log results
        if (!empty($processed)) {
            prorank_log(sprintf(
                '[ProRank Image Optimization] Processed %d failed optimizations from queue',
                count($processed)
            ));
        }
        
        // Reschedule if items remain
        if (!empty($remaining)) {
            wp_schedule_single_event(time() + 7200, 'prorank_retry_failed_optimizations');
        }
    }
    
    /**
     * Backup original image before optimization
     *
     * @param string $file_path     Original file path
     * @param int    $attachment_id Attachment ID
     * @return bool Success status
     */
    private function backup_original_image(string $file_path, int $attachment_id): bool {
        if ($file_path === '' || !file_exists($file_path) || !is_readable($file_path)) {
            update_post_meta($attachment_id, '_prorank_backup_failure_reason', 'source_file_unavailable');
            return false;
        }

        $existing_backup_path = (string) get_post_meta($attachment_id, '_prorank_backup_path', true);
        if ($existing_backup_path !== '' && file_exists($existing_backup_path)) {
            delete_post_meta($attachment_id, '_prorank_backup_failure_reason');
            return true;
        }

        $time = current_time('mysql');
        $y = substr($time, 0, 4);
        $m = substr($time, 5, 2);

        foreach ($this->get_all_image_backup_directories() as $backup_dir) {
            if (!$this->prepare_backup_root($backup_dir)) {
                continue;
            }

            $backup_subdir = $backup_dir . "/$y/$m";
            if (!$this->ensure_backup_directory($backup_subdir)) {
                continue;
            }

            $filename = basename($file_path);
            $backup_filename = $attachment_id . '-' . $filename;
            $backup_path = $backup_subdir . '/' . $backup_filename;

            if (file_exists($backup_path) || @copy($file_path, $backup_path)) {
                update_post_meta($attachment_id, '_prorank_backup_path', $backup_path);
                delete_post_meta($attachment_id, '_prorank_backup_failure_reason');
                return true;
            }
        }

        update_post_meta($attachment_id, '_prorank_backup_failure_reason', 'backup_copy_failed');
        return false;
    }

    /**
     * Ensure a backup directory exists and is writable.
     *
     * @param string $directory Directory path.
     * @return bool
     */
    private function ensure_backup_directory(string $directory): bool {
        if ($directory === '') {
            return false;
        }

        if (!file_exists($directory) && !wp_mkdir_p($directory)) {
            return false;
        }

        return is_dir($directory) && is_writable($directory);
    }

    /**
     * Create and protect the backup root before using it.
     *
     * @param string $backup_dir Backup directory path.
     * @return bool
     */
    private function prepare_backup_root(string $backup_dir): bool {
        if (!$this->ensure_backup_directory($backup_dir)) {
            return false;
        }

        $htaccess = $backup_dir . '/.htaccess';
        if (!file_exists($htaccess)) {
            @file_put_contents($htaccess, "Require all denied\n<IfModule !mod_authz_core.c>\nOrder Deny,Allow\nDeny from all\n</IfModule>\n");
        }

        $index_file = $backup_dir . '/index.php';
        if (!file_exists($index_file)) {
            @file_put_contents($index_file, "<?php\n// Silence is golden.\n");
        }

        return true;
    }
    
    /**
     * Schedule background optimization cron job
     *
     * @return void
     */
    public function schedule_background_optimization(): void {
        if (!wp_next_scheduled('prorank_background_image_optimization')) {
            wp_schedule_event(time(), 'hourly', 'prorank_background_image_optimization');
        }
    }
    
    /**
     * Run background optimization
     *
     * @return void
     */
    public function run_background_optimization(): void {
        // Check if already running
        if (get_transient('prorank_background_optimization_running')) {
            return;
        }
        
        // Set running flag
        set_transient('prorank_background_optimization_running', true, HOUR_IN_SECONDS);
        
        // Get unoptimized images
        $args = [
            'post_type' => 'attachment',
            'post_mime_type' => 'image',
            'post_status' => 'inherit',
            'posts_per_page' => 10, // Process 10 images per cron run
            'meta_query' => [
                [
                    'key' => '_prorank_optimized',
                    'compare' => 'NOT EXISTS',
                ],
            ],
        ];
        
        $attachments = get_posts($args);
        
        foreach ($attachments as $attachment) {
            $metadata = wp_get_attachment_metadata($attachment->ID);
            if ($metadata) {
                $this->generate_optimized_images($metadata, $attachment->ID);
            }
        }
        
        // Clear running flag
        delete_transient('prorank_background_optimization_running');
    }

    /**
     * Process bulk optimization (triggered by REST API)
     *
     * Handles the prorank_bulk_optimize_images cron action scheduled by
     * ImageOptimizationSettingsController::start_bulk_optimization()
     *
     * @return void
     */
    public function process_bulk_optimization(): void {
        // Get unoptimized images - process in batches of 20
        $batch_size = 20;
        $processed = 0;
        $max_execution = 50; // Max images per cron run

        do {
            $args = [
                'post_type' => 'attachment',
                'post_mime_type' => 'image',
                'post_status' => 'inherit',
                'posts_per_page' => $batch_size,
                'meta_query' => [
                    'relation' => 'OR',
                    [
                        'key' => '_prorank_optimized',
                        'compare' => 'NOT EXISTS',
                    ],
                    [
                        'key' => '_prorank_optimized',
                        'value' => '',
                    ],
                ],
            ];

            $attachments = get_posts($args);

            if (empty($attachments)) {
                break;
            }

            foreach ($attachments as $attachment) {
                // Check memory and time limits
                if (memory_get_usage(true) > (wp_convert_hr_to_bytes(ini_get('memory_limit')) * 0.8)) {
                    break 2;
                }

                $metadata = wp_get_attachment_metadata($attachment->ID);
                if ($metadata) {
                    $this->generate_optimized_images($metadata, $attachment->ID);
                } else {
                    // Mark as processed even without metadata to avoid infinite loop
                    update_post_meta($attachment->ID, '_prorank_optimized', '0');
                }

                $processed++;

                if ($processed >= $max_execution) {
                    break 2;
                }
            }
        } while (!empty($attachments) && $processed < $max_execution);

        // Check if more images remain
        $remaining = get_posts([
            'post_type' => 'attachment',
            'post_mime_type' => 'image',
            'post_status' => 'inherit',
            'posts_per_page' => 1,
            'fields' => 'ids',
            'meta_query' => [
                'relation' => 'OR',
                [
                    'key' => '_prorank_optimized',
                    'compare' => 'NOT EXISTS',
                ],
                [
                    'key' => '_prorank_optimized',
                    'value' => '',
                ],
            ],
        ]);

        if (!empty($remaining)) {
            // Schedule next batch
            if (!wp_next_scheduled('prorank_bulk_optimize_images')) {
                wp_schedule_single_event(time() + 30, 'prorank_bulk_optimize_images');
            }
        } else {
            // All done - clear running flag
            delete_transient('prorank_bulk_optimization_running');
        }
    }

    /**
     * Add custom media menu
     *
     * @return void
     */
    public function add_custom_media_menu(): void {
        add_media_page(
            __('Custom Media', 'prorank-seo'),
            __('Custom Media', 'prorank-seo'),
            'manage_options',
            'prorank-custom-media',
            [$this, 'render_custom_media_page']
        );
    }
    
    /**
     * Render custom media page
     *
     * @return void
     */
    public function render_custom_media_page(): void {
        ?>
        <div class="wrap">
            <h1><?php echo esc_html__('Custom Media Optimization', 'prorank-seo'); ?></h1>
            <p><?php echo esc_html__('Optimize images from custom folders not managed by WordPress Media Library.', 'prorank-seo'); ?></p>
            
            <div id="prorank-custom-media-root"></div>
        </div>
        <?php
    }
    
    /**
     * Restore original image from backup
     *
     * @param int $attachment_id Attachment ID
     * @return bool Success status
     */
    public function restore_from_backup(int $attachment_id): bool {
        $backup_path = get_post_meta($attachment_id, '_prorank_backup_path', true);
        
        if (!$backup_path || !file_exists($backup_path)) {
            return false;
        }
        
        $file_path = get_attached_file($attachment_id);
        if (!$file_path) {
            return false;
        }
        
        // Restore original file
        if (copy($backup_path, $file_path)) {
            // Delete optimized versions
            $this->delete_optimized_images($attachment_id);

            $this->reset_attachment_optimization_state($attachment_id);
            
            // Regenerate thumbnails
            $metadata = wp_get_attachment_metadata($attachment_id);
            if ($metadata) {
                wp_update_attachment_metadata($attachment_id, $metadata);
            }
            
            return true;
        }
        
        return false;
    }
    
    /**
     * Get directory size recursively
     *
     * @param string $dir Directory path
     * @return int Size in bytes
     */
    private function get_directory_size(string $dir): int {
        $size = 0;
        
        if (!is_dir($dir)) {
            return $size;
        }
        
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );
        
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }
        
        return $size;
    }
    
    /**
     * Delete all backup files
     *
     * @return bool Success status
     */
    public function delete_all_backups(): bool {
        // Use WordPress filesystem API
        global $wp_filesystem;
        if (!function_exists('WP_Filesystem')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }
        
        WP_Filesystem();
        
        foreach ($this->get_all_image_backup_directories() as $backup_dir) {
            if ($wp_filesystem->exists($backup_dir) && !$wp_filesystem->delete($backup_dir, true)) {
                return false;
            }
        }

        $attachment_ids = get_posts([
            'post_type'      => 'attachment',
            'post_status'    => 'inherit',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'meta_query'     => [
                [
                    'key'     => '_prorank_backup_path',
                    'compare' => 'EXISTS',
                ],
            ],
        ]);

        foreach ($attachment_ids as $attachment_id) {
            delete_post_meta((int) $attachment_id, '_prorank_backup_path');
        }
        
        return true;
    }

    /**
     * Restore all images from backups
     *
     * @return array{restored:int,failed:int,missing_files:int}
     */
    public function restore_all_backups() {
        $args = [
            'post_type'      => 'attachment',
            'post_status'    => 'inherit',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'meta_query'     => [
                [
                    'key'     => '_prorank_backup_path',
                    'compare' => 'EXISTS',
                ],
            ],
        ];

        $ids = get_posts($args);
        $restored = 0;
        $failed = 0;
        $missing_files = 0;

        foreach ($ids as $attachment_id) {
            $backup_path = (string) get_post_meta((int) $attachment_id, '_prorank_backup_path', true);
            if ($backup_path === '' || !file_exists($backup_path)) {
                $failed++;
                $missing_files++;
                continue;
            }

            $ok = $this->restore_from_backup((int) $attachment_id);
            if ($ok) {
                $restored++;
            } else {
                $failed++;
            }
        }

        return [
            'restored' => $restored,
            'failed' => $failed,
            'missing_files' => $missing_files,
        ];
    }

    /**
     * Store image backups outside the public uploads directory.
     */
    private function get_image_backup_directory(): string {
        $site_root        = wp_normalize_path(untrailingslashit(ABSPATH));
        $site_parent_root = wp_normalize_path(dirname($site_root));
        $site_fingerprint = md5((string) home_url('/'));

        return trailingslashit($site_parent_root) . 'prorank-image-backups-' . $site_fingerprint;
    }

    /**
     * Legacy backup location used by older builds.
     */
    private function get_legacy_image_backup_directory(): string {
        $upload_dir = wp_upload_dir();

        return trailingslashit($upload_dir['basedir']) . 'prorank-backups';
    }

    /**
     * Backup directories that may contain image backups.
     *
     * @return array<int, string>
     */
    private function get_all_image_backup_directories(): array {
        return array_values(
            array_unique(
                array_map(
                    'wp_normalize_path',
                    [
                        $this->get_image_backup_directory(),
                        $this->get_legacy_image_backup_directory(),
                    ]
                )
            )
        );
    }

    /**
     * Meta keys that point to generated derivative files, excluding the original backup copy.
     *
     * @param int $attachment_id Attachment ID.
     * @return array<int, string>
     */
    private function get_optimized_path_meta_keys(int $attachment_id): array {
        return array_values(
            array_filter(
                array_map(
                    'strval',
                    (array) $GLOBALS['wpdb']->get_col(
                        $GLOBALS['wpdb']->prepare(
                            "SELECT meta_key FROM {$GLOBALS['wpdb']->postmeta}
                             WHERE post_id = %d
                             AND meta_key LIKE '_prorank_%_path%'",
                            $attachment_id
                        )
                    )
                ),
                static function (string $meta_key): bool {
                    return $meta_key !== '_prorank_backup_path';
                }
            )
        );
    }

    /**
     * Clear optimization state so restored images can be optimized again cleanly.
     *
     * @param int $attachment_id Attachment ID.
     * @return void
     */
    private function reset_attachment_optimization_state(int $attachment_id): void {
        $meta_keys = [
            '_prorank_optimized',
            '_prorank_optimization_date',
            '_prorank_optimization_failed',
            '_prorank_optimization_failure_reason',
            '_prorank_optimization_no_gain',
            '_prorank_optimization_skip_reason',
            '_prorank_optimization_attempts',
            '_prorank_bytes_saved',
            '_prorank_original_source_bytes_saved',
            '_prorank_webp_generated',
            '_prorank_avif_generated',
            '_prorank_jxl_generated',
            '_prorank_original_size',
            '_prorank_optimization_method',
        ];

        foreach ($meta_keys as $meta_key) {
            delete_post_meta($attachment_id, $meta_key);
        }
    }

    /**
     * Remove the terminal no-gain state so the image can be evaluated again.
     *
     * @param int $attachment_id Attachment ID.
     * @return void
     */
    private function clear_attachment_no_gain_state(int $attachment_id): void {
        delete_post_meta($attachment_id, '_prorank_optimization_no_gain');
        delete_post_meta($attachment_id, '_prorank_optimization_skip_reason');
    }

    /**
     * Mark an attachment as terminally unchanged because no worthwhile optimized output was produced.
     *
     * @param int    $attachment_id Attachment ID.
     * @param string $reason Skip reason slug.
     * @return void
     */
    private function mark_attachment_no_gain(int $attachment_id, string $reason = 'no_effective_output'): void {
        delete_post_meta($attachment_id, '_prorank_optimized');
        delete_post_meta($attachment_id, '_prorank_optimization_date');
        delete_post_meta($attachment_id, '_prorank_optimization_failed');
        delete_post_meta($attachment_id, '_prorank_optimization_failure_reason');
        delete_post_meta($attachment_id, '_prorank_optimization_attempts');
        update_post_meta($attachment_id, '_prorank_optimization_no_gain', '1');
        update_post_meta($attachment_id, '_prorank_optimization_skip_reason', $reason);
    }

    /**
     * Determine whether an attachment is in the terminal no-gain state.
     *
     * @param int $attachment_id Attachment ID.
     * @return bool
     */
    private function is_attachment_no_gain(int $attachment_id): bool {
        if ((string) get_post_meta($attachment_id, '_prorank_optimization_no_gain', true) === '1') {
            return true;
        }

        return (string) get_post_meta($attachment_id, '_prorank_optimization_failure_reason', true) === 'no_effective_output';
    }

    /**
     * Get the no-gain reason for an attachment, supporting legacy failure metadata.
     *
     * @param int $attachment_id Attachment ID.
     * @return string
     */
    private function get_attachment_no_gain_reason(int $attachment_id): string {
        $reason = (string) get_post_meta($attachment_id, '_prorank_optimization_skip_reason', true);
        if ($reason !== '') {
            return $reason;
        }

        $legacy_reason = (string) get_post_meta($attachment_id, '_prorank_optimization_failure_reason', true);
        if ($legacy_reason === 'no_effective_output') {
            return $legacy_reason;
        }

        return '';
    }

    /**
     * Resolve the per-attachment retry limit for failed bulk optimizations.
     *
     * @return int
     */
    private function get_bulk_retry_limit(): int {
        return max(1, (int) apply_filters('prorank_image_optimization_retry_limit', self::DEFAULT_BULK_RETRY_LIMIT));
    }
    
    /**
    /**
     * Get available PHP memory in bytes (0 if unlimited).
     *
     * @return int
     */
    private function get_available_memory(): int {
        $limit = ini_get('memory_limit');
        if ($limit === '-1' || $limit === '' || $limit === false) {
            return 0; // unlimited
        }
        $limit = wp_convert_hr_to_bytes($limit);
        $used  = memory_get_usage(true);
        return max(0, $limit - $used);
    }

    /**
     * Check if file should be excluded from optimization
     *
     * @param string $file_path     File path
     * @param int    $attachment_id Attachment ID
     * @param string $size          Image size (optional)
     * @return bool True if should be excluded
     */
    private function should_exclude_file(string $file_path, int $attachment_id, string $size = ''): bool {
        if ($this->matches_exclude_path($file_path)) {
            return true;
        }

        if ($attachment_id > 0) {
            $attachment_url = wp_get_attachment_url($attachment_id);
            if (is_string($attachment_url) && $attachment_url !== '' && $this->matches_exclude_path($attachment_url)) {
                return true;
            }
        }

        // Check size exclusions
        $exclude_sizes = $this->get_setting('exclude_sizes', []);
        if (!empty($size) && in_array($size, $exclude_sizes, true)) {
            return true;
        }
        
        // Check pattern exclusions
        $exclude_patterns = $this->get_setting('exclude_patterns', []);
        if (empty($exclude_patterns)) {
            return false;
        }
        
        // Get file info
        $filename = basename($file_path);
        $filesize = filesize($file_path);
        
        foreach ($exclude_patterns as $pattern) {
            $matches = false;
            
            switch ($pattern['type']) {
                case 'filename':
                    $matches = strpos($filename, $pattern['pattern']) !== false;
                    break;
                    
                case 'path':
                    $matches = strpos($file_path, $pattern['pattern']) !== false;
                    break;
                    
                case 'size':
                    $operator = substr($pattern['pattern'], 0, 1);
                    $kb = (int) substr($pattern['pattern'], 1);
                    $bytes = $kb * 1024;
                    
                    if ($operator === '<') {
                        $matches = $filesize < $bytes;
                    } elseif ($operator === '>') {
                        $matches = $filesize > $bytes;
                    }
                    break;
            }
            
            if ($matches) {
                // Check target applicability
                switch ($pattern['target']) {
                    case 'all':
                        return true;
                        
                    case 'thumbnails':
                        // Exclude only if it's a thumbnail (not full/scaled)
                        return !empty($size) && $size !== 'full' && $size !== 'scaled';
                        
                    case 'custom_media':
                        // This would need additional context to determine
                        return false;
                        
                    case 'specific_sizes':
                        return !empty($size) && in_array($size, $pattern['sizes'], true);
                }
            }
        }
        
        return false;
    }
    
    /**
     * Get all registered image sizes
     *
     * @return array Array of image sizes with details
     */
    public function get_image_sizes(): array {
        global $_wp_additional_image_sizes;
        
        $sizes = [];
        
        // Default WordPress sizes
        $default_sizes = ['thumbnail', 'medium', 'medium_large', 'large'];
        
        foreach ($default_sizes as $size) {
            $sizes[] = [
                'name' => $size,
                'label' => ucfirst(str_replace('_', ' ', $size)),
                'width' => get_option($size . '_size_w'),
                'height' => get_option($size . '_size_h'),
                'crop' => get_option($size . '_crop', false),
            ];
        }
        
        // Additional sizes
        if (isset($_wp_additional_image_sizes) && is_array($_wp_additional_image_sizes)) {
            foreach ($_wp_additional_image_sizes as $name => $data) {
                $sizes[] = [
                    'name' => $name,
                    'label' => ucfirst(str_replace(['_', '-'], ' ', $name)),
                    'width' => $data['width'],
                    'height' => $data['height'],
                    'crop' => $data['crop'],
                ];
            }
        }
        
        return $sizes;
    }
    
    /**
     * Convert images in content to picture elements
     *
     * @param string $content Post content
     * @return string Modified content
     */
    public function convert_content_images(string $content): string {
        if (!$this->is_frontend_optimization_context()) {
            return $content;
        }

        // Skip if delivery is disabled
        if (!$this->get_setting('enable_picture_element', true)) {
            return $content;
        }
        
        // Skip if no images
        if (stripos($content, '<img') === false) {
            return $content;
        }
        
        // Use DOMDocument to parse HTML
        $doc = new \DOMDocument();
        libxml_use_internal_errors(true);
        @$doc->loadHTML('<?xml encoding="UTF-8"><div>' . $content . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        libxml_clear_errors();
        
        $xpath = new \DOMXPath($doc);
        $images = $xpath->query('//img[contains(@class, "wp-image-")]');
        
        if ($images->length === 0) {
            return $content;
        }
        
        $modified = false;
        
        foreach ($images as $img) {
            // Extract attachment ID from class
            $classes = $img->getAttribute('class');
            if (preg_match('/wp-image-(\d+)/', $classes, $matches)) {
                $attachment_id = (int) $matches[1];
                
                // Get original HTML for this image
                $img_html = $doc->saveHTML($img);
                
                // Convert to picture element
                $picture_html = $this->convert_to_picture_element($img_html, $attachment_id, '', false, []);
                
                // If conversion happened, replace in DOM
                if ($picture_html !== $img_html) {
                    $fragment = $doc->createDocumentFragment();
                    @$fragment->appendXML($picture_html);
                    if ($fragment->firstChild) {
                        $img->parentNode->replaceChild($fragment->firstChild, $img);
                        $modified = true;
                    }
                }
            }
        }
        
        if (!$modified) {
            return $content;
        }
        
        // Get the modified content
        $body = $xpath->query('//div')->item(0);
        if ($body) {
            $content = '';
            foreach ($body->childNodes as $child) {
                $content .= $doc->saveHTML($child);
            }
        }
        
        return $content;
    }
    
    /**
     * Get optimized image URL
     *
     * @param string $original_url Original image URL
     * @param string $format       Target format (webp/avif)
     * @return string Optimized URL
     */
    private function get_optimized_url(string $original_url, string $format): string {
        $pathinfo = pathinfo($original_url);
        return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '.' . $format;
    }
    
    /**
     * Check if optimized file exists
     * Also checks for WordPress-suffixed versions (e.g., filename-2.webp)
     *
     * @param string $url Optimized file URL
     * @return bool
     */
    private function optimized_file_exists(string $url): bool {
        // Convert URL to path
        $upload_dir = wp_upload_dir();
        $path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $url);

        // Check exact match first
        if (file_exists($path) && filesize($path) > 0) {
            return true;
        }

        // Check for WordPress-suffixed versions (e.g., filename-2.webp, filename-3.webp)
        // WordPress adds numeric suffixes to prevent filename collisions
        $pathinfo = pathinfo($path);
        for ($i = 2; $i <= 10; $i++) {
            $suffixed_path = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '-' . $i . '.' . $pathinfo['extension'];
            if (file_exists($suffixed_path) && filesize($suffixed_path) > 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get actual optimized file URL (handles WordPress suffixes)
     *
     * @param string $url Expected optimized URL
     * @return string|null Actual URL if exists, null otherwise
     */
    private function get_actual_optimized_url(string $url): ?string {
        $upload_dir = wp_upload_dir();
        $path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $url);

        // Check exact match first
        if (file_exists($path) && filesize($path) > 0) {
            return $url;
        }

        // Check for WordPress-suffixed versions
        // WordPress adds -N suffix BEFORE dimensions, e.g., image-2-300x187.webp (not image-300x187-2.webp)
        $pathinfo = pathinfo($path);
        $url_pathinfo = pathinfo($url);
        $filename = $pathinfo['filename'];
        $url_filename = $url_pathinfo['filename'];

        // Check if filename has dimension suffix like -300x187
        $dimension_pattern = '/-(\d+x\d+)$/';
        $has_dimensions = preg_match($dimension_pattern, $filename, $dim_matches);

        for ($i = 2; $i <= 10; $i++) {
            if ($has_dimensions) {
                // Insert -N before the dimensions: image-300x187 -> image-2-300x187
                $base_filename = preg_replace($dimension_pattern, '', $filename);
                $dimensions = $dim_matches[1];
                $suffixed_path = $pathinfo['dirname'] . '/' . $base_filename . '-' . $i . '-' . $dimensions . '.' . $pathinfo['extension'];

                if (file_exists($suffixed_path) && filesize($suffixed_path) > 0) {
                    $base_url_filename = preg_replace($dimension_pattern, '', $url_filename);
                    return $url_pathinfo['dirname'] . '/' . $base_url_filename . '-' . $i . '-' . $dimensions . '.' . $url_pathinfo['extension'];
                }
            }

            // Also check traditional suffix at end (for non-sized images)
            $suffixed_path = $pathinfo['dirname'] . '/' . $filename . '-' . $i . '.' . $pathinfo['extension'];
            if (file_exists($suffixed_path) && filesize($suffixed_path) > 0) {
                return $url_pathinfo['dirname'] . '/' . $url_filename . '-' . $i . '.' . $url_pathinfo['extension'];
            }
        }

        return null;
    }
    
    /**
     * Convert PNG to JPEG
     *
     * @param array $metadata      Attachment metadata
     * @param int   $attachment_id Attachment ID
     * @return array Modified metadata
     */
    public function convert_png_to_jpeg(array $metadata, int $attachment_id): array {
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            return $metadata;
        }
        
        // Check if it's a PNG
        $mime_type = get_post_mime_type($attachment_id);
        if ($mime_type !== 'image/png') {
            return $metadata;
        }
        
        // Check if it has transparency
        if ($this->png_has_transparency($file_path)) {
            // Keep PNG if it has transparency
            return $metadata;
        }
        
        // Convert to JPEG
        $jpeg_path = preg_replace('/\.png$/i', '.jpg', $file_path);
        
        $success = false;
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($file_path);
                $imagick->setImageFormat('jpeg');
                $imagick->setImageCompressionQuality($this->get_jpeg_quality());
                $imagick->setImageBackgroundColor('white');
                $imagick = $imagick->flattenImages();
                $success = $imagick->writeImage($jpeg_path);
                $imagick->destroy();
            } catch (\Exception $e) {
                // Fall back to GD
            }
        }
        
        if (!$success && function_exists('imagecreatefromstring')) {
            $image = imagecreatefromstring(file_get_contents($file_path));
            if ($image) {
                // Create a white background
                $width = imagesx($image);
                $height = imagesy($image);
                $jpeg_image = imagecreatetruecolor($width, $height);
                $white = imagecolorallocate($jpeg_image, 255, 255, 255);
                imagefill($jpeg_image, 0, 0, $white);
                imagecopy($jpeg_image, $image, 0, 0, 0, 0, $width, $height);
                
                $success = imagejpeg($jpeg_image, $jpeg_path, $this->get_jpeg_quality());
                imagedestroy($image);
                imagedestroy($jpeg_image);
            }
        }
        
        if ($success) {
            // Update attachment
            update_attached_file($attachment_id, $jpeg_path);
            wp_update_post([
                'ID' => $attachment_id,
                'post_mime_type' => 'image/jpeg',
            ]);
            
            // Delete original PNG
            \ProRank\SEO\Core\FilesystemHelper::delete_file($file_path);
            
            // Update metadata
            $metadata = wp_generate_attachment_metadata($attachment_id, $jpeg_path);
        }
        
        return $metadata;
    }
    
    /**
     * Convert CMYK to RGB
     *
     * @param array $metadata      Attachment metadata
     * @param int   $attachment_id Attachment ID
     * @return array Modified metadata
     */
    public function convert_cmyk_to_rgb(array $metadata, int $attachment_id): array {
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            return $metadata;
        }
        
        // Check if it's CMYK
        if (!$this->is_cmyk_image($file_path)) {
            return $metadata;
        }
        
        $success = false;
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($file_path);
                if ($imagick->getImageColorspace() == \Imagick::COLORSPACE_CMYK) {
                    $imagick->transformImageColorspace(\Imagick::COLORSPACE_RGB);
                    $success = $imagick->writeImage($file_path);
                    $imagick->destroy();
                    
                    if ($success) {
                        // Regenerate metadata
                        $metadata = wp_generate_attachment_metadata($attachment_id, $file_path);
                    }
                }
            } catch (\Exception $e) {
                // Log error
            }
        }
        
        return $metadata;
    }
    
    /**
     * Manage EXIF data
     *
     * @param array $metadata      Attachment metadata
     * @param int   $attachment_id Attachment ID
     * @return array Modified metadata
     */
    public function manage_exif_data(array $metadata, int $attachment_id): array {
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            return $metadata;
        }
        
        // Check if it's a JPEG
        $mime_type = get_post_mime_type($attachment_id);
        if ($mime_type !== 'image/jpeg') {
            return $metadata;
        }
        
        // Remove EXIF data
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($file_path);
                
                // Keep orientation data if needed
                $orientation = $imagick->getImageOrientation();
                
                // Strip all EXIF data
                $imagick->stripImage();
                
                // Restore orientation
                if ($orientation !== \Imagick::ORIENTATION_UNDEFINED) {
                    $imagick->setImageOrientation($orientation);
                }
                
                $imagick->writeImage($file_path);
                $imagick->destroy();
            } catch (\Exception $e) {
                // Log error
            }
        }
        
        return $metadata;
    }
    
    /**
     * Optimize PDF files
     *
     * @param array $metadata      Attachment metadata
     * @param int   $attachment_id Attachment ID
     * @return array Modified metadata
     */
    public function optimize_pdf(array $metadata, int $attachment_id): array {
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            return $metadata;
        }
        
        // Check if it's a PDF
        $mime_type = get_post_mime_type($attachment_id);
        if ($mime_type !== 'application/pdf') {
            return $metadata;
        }
        
        // Backup original
        if ($this->get_setting('backup_originals', true) && !$this->backup_original_image($file_path, $attachment_id)) {
            update_post_meta($attachment_id, '_prorank_optimization_failed', '1');
            update_post_meta($attachment_id, '_prorank_optimization_failure_reason', 'backup_original_failed');
            return $metadata;
        }
        
        // Use Imagick if available
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick();
                $imagick->setResolution(150, 150);
                $imagick->readImage($file_path);
                $imagick->setImageCompression(\Imagick::COMPRESSION_JPEG);
                $imagick->setImageCompressionQuality(85);
                $imagick->writeImage($file_path);
                $imagick->destroy();
                
                // Update optimization status
                update_post_meta($attachment_id, '_prorank_pdf_optimized', true);
            } catch (\Exception $e) {
                // Log error
            }
        }
        
        return $metadata;
    }
    
    /**
     * Generate retina images
     *
     * @param array $metadata      Attachment metadata
     * @param int   $attachment_id Attachment ID
     * @return array Modified metadata
     */
    public function generate_retina_images(array $metadata, int $attachment_id): array {
        $file_path = get_attached_file($attachment_id);
        if (!$file_path || !file_exists($file_path)) {
            return $metadata;
        }
        
        // Check if it's an image
        if (!wp_attachment_is_image($attachment_id)) {
            return $metadata;
        }
        
        // Generate 2x version for each size
        if (!empty($metadata['sizes'])) {
            $upload_dir = wp_upload_dir();
            $base_dir = dirname($file_path);
            
            foreach ($metadata['sizes'] as $size => $size_data) {
                $size_path = $base_dir . '/' . $size_data['file'];
                if (file_exists($size_path)) {
                    $this->generate_retina_version($size_path, $size_data['width'] * 2, $size_data['height'] * 2);
                }
            }
        }
        
        // Generate 2x for full size
        $size = wp_getimagesize($file_path);
        if ($size && $size[0] > 0 && $size[1] > 0) {
            $this->generate_retina_version($file_path, $size[0] * 2, $size[1] * 2);
        }
        
        return $metadata;
    }
    
    /**
     * Add AI training protection
     *
     * @param int $attachment_id Attachment ID
     * @return void
     */
    public function add_ai_training_protection(int $attachment_id): void {
        // Add AI training protection meta
        update_post_meta($attachment_id, '_prorank_no_ai_training', '1');
        
        // Add to protected content list
        $protected_images = get_option('prorank_ai_protected_images', []);
        if (!in_array($attachment_id, $protected_images)) {
            $protected_images[] = $attachment_id;
            update_option('prorank_ai_protected_images', $protected_images);
        }
    }
    
    /**
     * Add noai attributes to images
     *
     * @param array $attr       Image attributes
     * @param object $attachment Attachment object
     * @param string|array $size Image size
     * @return array Modified attributes
     */
    public function add_noai_attributes(array $attr, $attachment, $size): array {
        $no_ai = get_post_meta($attachment->ID, '_prorank_no_ai_training', true);
        
        if ($no_ai === '1') {
            // Add data attribute for AI training prevention
            $attr['data-noai'] = '1';
            $attr['data-no-ai-training'] = 'true';
            
            // Add to alt text if configured
            if ($this->get_setting('add_noai_to_alt', false)) {
                $attr['alt'] = ($attr['alt'] ?? '') . ' [No AI Training]';
            }
        }
        
        return $attr;
    }
    
    /**
     * Get JPEG quality based on compression type
     *
     * @return int Quality value
     */
    private function get_jpeg_quality(): int {
        $compression_type = $this->get_setting('compression_type', 'lossy');
        $quality = self::COMPRESSION_TYPES[$compression_type]['jpeg_quality'] ?? 85;
        
        // Override with custom quality if SmartCompression is disabled
        if (!$this->get_setting('smart_compression', true)) {
            $quality = $this->get_setting('jpeg_quality', 85);
        }
        
        return $quality;
    }
    
    /**
     * Check if PNG has transparency
     *
     * @param string $file_path File path
     * @return bool
     */
    private function png_has_transparency(string $file_path): bool {
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($file_path);
                $has_alpha = $imagick->getImageAlphaChannel();
                $imagick->destroy();
                return $has_alpha === \Imagick::ALPHACHANNEL_ACTIVATE;
            } catch (\Exception $e) {
                // Fall back to GD
            }
        }

        if (function_exists('imagecreatefrompng')) {
            $image = @imagecreatefrompng($file_path);
            if ($image) {
                $width = imagesx($image);
                $height = imagesy($image);
                $total_pixels = $width * $height;

                // Use sampling for large images to avoid O(n²) performance issues
                // Sample up to 2000 random pixels - statistically sufficient to detect transparency
                $max_samples = min(2000, $total_pixels);

                if ($total_pixels <= 2000) {
                    // Small image - scan all pixels (still fast)
                    for ($x = 0; $x < $width; $x++) {
                        for ($y = 0; $y < $height; $y++) {
                            $rgba = imagecolorat($image, $x, $y);
                            $alpha = ($rgba & 0x7F000000) >> 24;
                            if ($alpha > 0) {
                                imagedestroy($image);
                                return true;
                            }
                        }
                    }
                } else {
                    // Large image - use random sampling + edge scanning
                    // First check corners and edges (common places for transparency)
                    $edge_points = [
                        [0, 0], [$width - 1, 0], [0, $height - 1], [$width - 1, $height - 1], // corners
                        [(int)($width / 2), 0], [(int)($width / 2), $height - 1], // top/bottom center
                        [0, (int)($height / 2)], [$width - 1, (int)($height / 2)], // left/right center
                    ];

                    foreach ($edge_points as $point) {
                        $rgba = imagecolorat($image, $point[0], $point[1]);
                        $alpha = ($rgba & 0x7F000000) >> 24;
                        if ($alpha > 0) {
                            imagedestroy($image);
                            return true;
                        }
                    }

                    // Then sample random pixels throughout the image
                    for ($i = 0; $i < $max_samples; $i++) {
                        $x = wp_rand(0, $width - 1);
                        $y = wp_rand(0, $height - 1);
                        $rgba = imagecolorat($image, $x, $y);
                        $alpha = ($rgba & 0x7F000000) >> 24;
                        if ($alpha > 0) {
                            imagedestroy($image);
                            return true;
                        }
                    }
                }
                imagedestroy($image);
            }
        }

        return false;
    }
    
    /**
     * Check if image is CMYK
     *
     * @param string $file_path File path
     * @return bool
     */
    private function is_cmyk_image(string $file_path): bool {
        if (extension_loaded('imagick') && class_exists('Imagick')) {
            try {
                $imagick = new \Imagick($file_path);
                $colorspace = $imagick->getImageColorspace();
                $imagick->destroy();
                return $colorspace === \Imagick::COLORSPACE_CMYK;
            } catch (\Exception $e) {
                // Log error
            }
        }
        
        return false;
    }
    
    /**
     * Generate retina version of image
     *
     * @param string $file_path Source file path
     * @param int $width Target width
     * @param int $height Target height
     * @return bool Success status
     */
    private function generate_retina_version(string $file_path, int $width, int $height): bool {
        $pathinfo = pathinfo($file_path);
        $retina_path = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '@2x.' . $pathinfo['extension'];
        
        // Skip if already exists
        if (file_exists($retina_path)) {
            return true;
        }
        
        // Skip if source is smaller than target
        $size = wp_getimagesize($file_path);
        if (!$size || $size[0] < $width || $size[1] < $height) {
            return false;
        }
        
        $editor = wp_get_image_editor($file_path);
        if (!is_wp_error($editor)) {
            $editor->resize($width, $height, true);
            $result = $editor->save($retina_path);
            return !is_wp_error($result);
        }
        
        return false;
    }
    
    /**
     * Smart crop and resize image
     *
     * @param string $file_path Source file path
     * @param int $width Target width
     * @param int $height Target height
     * @param array $options Crop options
     * @return bool Success status
     */
    public function smart_crop_resize(string $file_path, int $width, int $height, array $options = []): bool {
        $defaults = [
            'crop_focus' => 'center', // center, faces, entropy, attention
            'upscale' => false,
            'quality' => $this->get_jpeg_quality(),
        ];
        
        $options = wp_parse_args($options, $defaults);
        
        // Use cloud service if enabled and supports smart crop
        $method = $this->get_optimization_method();
        if (in_array($method, ['cloud', 'smart', 'prorank'], true)) {
            return $this->cloud_smart_crop($file_path, $width, $height, $options);
        }
        
        // Fallback to local crop
        $editor = wp_get_image_editor($file_path);
        if (is_wp_error($editor)) {
            return false;
        }
        
        // Get current dimensions
        $size = $editor->get_size();
        $orig_width = $size['width'];
        $orig_height = $size['height'];
        
        // Calculate crop position based on focus
        $crop_x = 0;
        $crop_y = 0;
        
        switch ($options['crop_focus']) {
            case 'top':
                $crop_y = 0;
                break;
            case 'bottom':
                $crop_y = $orig_height - $height;
                break;
            case 'left':
                $crop_x = 0;
                break;
            case 'right':
                $crop_x = $orig_width - $width;
                break;
            case 'entropy':
                // Simple entropy-based crop (find area with most detail)
                list($crop_x, $crop_y) = $this->find_entropy_crop($file_path, $width, $height);
                break;
            default: // center
                $crop_x = ($orig_width - $width) / 2;
                $crop_y = ($orig_height - $height) / 2;
        }
        
        // Crop and resize
        $result = $editor->crop($crop_x, $crop_y, $width, $height, $width, $height, false);
        if (is_wp_error($result)) {
            return false;
        }
        
        // Save
        $filetype = wp_check_filetype($file_path);
        $saved = $editor->save($file_path, $filetype['type'] ?: 'image/jpeg');
        return !is_wp_error($saved);
    }
    
    /**
     * Cloud-based smart crop
     *
     * @param string $file_path Source file path
     * @param int $width Target width
     * @param int $height Target height
     * @param array $options Crop options
     * @return bool Success status
     */
    private function cloud_smart_crop(string $file_path, int $width, int $height, array $options): bool {
        return false;
    }
    
    /**
     * Find optimal crop position based on entropy
     *
     * @param string $file_path Source file path
     * @param int $width Target width
     * @param int $height Target height
     * @return array [x, y] coordinates
     */
    private function find_entropy_crop(string $file_path, int $width, int $height): array {
        // Simple entropy calculation - find area with most variation
        // This is a simplified version - real implementation would be more sophisticated
        
        if (!extension_loaded('gd')) {
            return [0, 0];
        }
        
        $image = imagecreatefromstring(file_get_contents($file_path));
        if (!$image) {
            return [0, 0];
        }
        
        $orig_width = imagesx($image);
        $orig_height = imagesy($image);
        
        $best_x = 0;
        $best_y = 0;
        $max_entropy = 0;
        
        // Sample areas and find highest entropy
        $step = 10; // Sample every 10 pixels for performance
        
        for ($y = 0; $y <= $orig_height - $height; $y += $step) {
            for ($x = 0; $x <= $orig_width - $width; $x += $step) {
                $entropy = $this->calculate_area_entropy($image, $x, $y, $width, $height);
                if ($entropy > $max_entropy) {
                    $max_entropy = $entropy;
                    $best_x = $x;
                    $best_y = $y;
                }
            }
        }
        
        imagedestroy($image);
        
        return [$best_x, $best_y];
    }
    
    /**
     * Calculate entropy for an area of image
     *
     * @param resource $image GD image resource
     * @param int $x Start X
     * @param int $y Start Y
     * @param int $width Area width
     * @param int $height Area height
     * @return float Entropy value
     */
    private function calculate_area_entropy($image, int $x, int $y, int $width, int $height): float {
        $histogram = array_fill(0, 256, 0);
        $total_pixels = $width * $height;
        
        // Build histogram of grayscale values
        for ($py = $y; $py < $y + $height; $py++) {
            for ($px = $x; $px < $x + $width; $px++) {
                $rgb = imagecolorat($image, $px, $py);
                $r = ($rgb >> 16) & 0xFF;
                $g = ($rgb >> 8) & 0xFF;
                $b = $rgb & 0xFF;
                
                // Convert to grayscale
                $gray = (int)(0.299 * $r + 0.587 * $g + 0.114 * $b);
                $histogram[$gray]++;
            }
        }
        
        // Calculate entropy
        $entropy = 0;
        foreach ($histogram as $count) {
            if ($count > 0) {
                $probability = $count / $total_pixels;
                $entropy -= $probability * log($probability, 2);
            }
        }
        
        return $entropy;
    }
    
    /**
     * Rewrite image URLs to use CDN
     *
     * @param string $url Original URL
     * @param int $attachment_id Attachment ID
     * @return string Modified URL
     */
    public function rewrite_cdn_url(string $url, int $attachment_id): string {
        $cdn_url = $this->get_setting('cdn_url', '');
        
        if (empty($cdn_url)) {
            return $url;
        }
        
        // Only rewrite for frontend
        if (!$this->is_frontend_optimization_context()) {
            return $url;
        }

        if ($this->is_current_request_excluded() || $this->matches_exclude_path($url)) {
            return $url;
        }
        
        // Parse URLs
        $site_url = get_site_url();
        $cdn_url = rtrim($cdn_url, '/');
        
        // Replace site URL with CDN URL
        if (strpos($url, $site_url) === 0) {
            $url = str_replace($site_url, $cdn_url, $url);
        }
        
        return $url;
    }
    
    /**
     * Rewrite srcset URLs to use CDN
     *
     * @param array $sources Sources array
     * @param array $size_array Size array
     * @param string $image_src Image source
     * @param array $image_meta Image metadata
     * @param int $attachment_id Attachment ID
     * @return array Modified sources
     */
    public function rewrite_srcset_cdn(array $sources, array $size_array, string $image_src, array $image_meta, int $attachment_id): array {
        $cdn_url = $this->get_setting('cdn_url', '');
        
        if (empty($cdn_url) || !$this->is_frontend_optimization_context()) {
            return $sources;
        }

        if ($this->is_current_request_excluded()) {
            return $sources;
        }
        
        $site_url = get_site_url();
        $cdn_url = rtrim($cdn_url, '/');
        
        foreach ($sources as &$source) {
            if (
                isset($source['url'])
                && strpos($source['url'], $site_url) === 0
                && !$this->matches_exclude_path((string) $source['url'])
            ) {
                $source['url'] = str_replace($site_url, $cdn_url, $source['url']);
            }
        }
        
        return $sources;
    }
    
    /**
     * Rewrite image URLs in content to use CDN
     *
     * @param string $content Post content
     * @return string Modified content
     */
    public function rewrite_content_cdn_urls(string $content): string {
        $cdn_url = $this->get_setting('cdn_url', '');
        
        if (empty($cdn_url) || !$this->is_frontend_optimization_context()) {
            return $content;
        }

        if ($this->is_current_request_excluded()) {
            return $content;
        }
        
        $site_url = get_site_url();
        $cdn_url = rtrim($cdn_url, '/');
        $upload_dir = wp_upload_dir();
        
        // Replace upload URLs with CDN
        $content = str_replace($upload_dir['baseurl'], str_replace($site_url, $cdn_url, $upload_dir['baseurl']), $content);
        
        return $content;
    }

    /**
     * Normalize opt-in timestamp value.
     *
     * @param mixed $value Opt-in timestamp value.
     * @return int|null
     */
    private function normalize_opt_in_timestamp($value): ?int {
        if (is_numeric($value)) {
            return (int) $value;
        }

        if (is_string($value)) {
            $timestamp = strtotime($value);
            if ($timestamp !== false) {
                return $timestamp;
            }
        }

        return null;
    }

    /**
     * Resolve the current quota period for cloud credits.
     *
     * @param array $license_status License status data.
     * @param bool  $opt_in_enabled Whether free opt-in credits are enabled.
     * @param int|null $opt_in_at   Opt-in timestamp (unix).
     * @return array
     */
    private function resolve_quota_period(array $license_status, bool $opt_in_enabled, ?int $opt_in_at): array {
        $now = time();
        $start = null;
        $end = null;
        $source = 'calendar';

        if ($opt_in_enabled) {
            $start = $opt_in_at ?: $now;
            $end = $start + YEAR_IN_SECONDS;
            $source = 'opt_in';
        }

        if (!$start || !$end) {
            $expires_raw = $license_status['expires'] ?? null;
            $expires = $expires_raw ? strtotime($expires_raw) : false;
            // Only trust license expiry as quota window if it is near-term.
            // Lifetime or very long expiries should use calendar-year quota windows.
            $max_window = YEAR_IN_SECONDS + (45 * DAY_IN_SECONDS);
            if ($expires && $expires > $now && ($expires - $now) <= $max_window) {
                $end = $expires;
                $start = $expires - YEAR_IN_SECONDS;
                $source = 'license';
            }
        }

        if (!$start || !$end) {
            $year = (int) gmdate('Y');
            $start = strtotime($year . '-01-01 00:00:00');
            $end = strtotime(($year + 1) . '-01-01 00:00:00');
            $source = 'calendar';
        }

        if ($end <= $now) {
            $start = $now;
            $end = $now + YEAR_IN_SECONDS;
            $source = 'rolling';
        }

        if ($start > $now) {
            $start = $now - YEAR_IN_SECONDS;
        }

        return [
            'start' => $start,
            'end' => $end,
            'source' => $source,
        ];
    }

    /**
     * Build a cache key for quota usage.
     *
     * @param int $start Period start timestamp.
     * @param int $end   Period end timestamp.
     * @return string
     */
    private function get_usage_cache_key(int $start, int $end): string {
        return 'prorank_image_usage_' . $start . '_' . $end;
    }

    /**
     * Build a cache key for the last normalized API quota payload.
     *
     * @return string
     */
    private function get_api_quota_status_cache_key(): string {
        return 'prorank_image_api_quota_status';
    }
    
    /**
     * Get image optimization quota status
     *
     * @return array Quota information
     */
    public function get_quota_status(): array {
        return [
            'status' => 'no_license',
            'message' => __('Cloud image optimization is not available in the free plugin.', 'prorank-seo'),
            'remaining' => 0,
            'used' => 0,
            'quota' => [
                'yearly' => 0,
            ],
            'quota_formatted' => [
                'yearly' => '0',
            ],
            'used_formatted' => '0',
            'remaining_formatted' => '0',
        ];

        // Check cached quota exceeded status
        $quota_exceeded = get_transient('prorank_image_quota_exceeded');
        if ($quota_exceeded) {
            return [
                'status' => 'exhausted',
                'message' => $quota_exceeded['message'],
                'remaining' => 0,
                'next_reset' => $quota_exceeded['next_reset'] ?? '',
            ];
        }

        $plugin = \ProRank\SEO\Plugin::get_instance();
        if (!$plugin || !$plugin->is_initialized()) {
            return [
                'status' => 'no_license',
                'message' => 'Unable to determine license status',
                'remaining' => 0,
            ];
        }

        $license_manager = $plugin->license();
        $license_status = $license_manager->get_license_status_snapshot();
        $tier = $license_status['tier'] ?? $license_manager->get_tier();
        $auth_token = $this->get_cloud_auth_token();
        $has_valid_license = $license_manager->has_valid_license_status();

        // Normalize tier names to support both base and *_plus variants
        $normalized_tier = $tier;
        if (strpos($normalized_tier, '_plus') !== false) {
            $normalized_tier = str_replace('_plus', '', $normalized_tier);
        }

        $cloud_opt_in = (bool) $this->get_setting('cloud_opt_in', false);
        $cloud_opt_in_at = $this->normalize_opt_in_timestamp($this->get_setting('cloud_opt_in_at', null));

        if (!$has_valid_license) {
            $tier = 'free';
            $normalized_tier = 'free';
        }

        $tier = $normalized_tier;

        // Base quotas per tier (images per year)
        $tier_quotas = [
            'free' => 0,
            'basic' => 0,
            'pro' => 5000,
            'business' => 20000,
            'agency' => 100000,
        ];

        $quota_total = $tier_quotas[$normalized_tier] ?? 0;
        $is_free_tier = ($normalized_tier === 'free');

        // Free opt-in credits (requires license key)
        if ($is_free_tier && $cloud_opt_in) {
            $quota_total = 50;
        }

        // Prefer server-provided limits but cap them to the tier matrix
        $license_limit = $license_status['limits']['image_optimization_credits'] ?? null;
        if ($license_limit !== null && $quota_total > 0) {
            $quota_total = min((int) $license_limit, (int) $quota_total);
        }

        if ($quota_total <= 0) {
            $message = $cloud_opt_in
                ? 'Connect your license to use free cloud credits'
                : 'Cloud optimization is available with Pro or free opt-in credits';

            return [
                'status' => 'no_license',
                'message' => $message,
                'remaining' => 0,
                'tier' => $tier,
                'upgrade_url' => 'https://prorank.io/pricing',
            ];
        }

        if (empty($auth_token)) {
            return [
                'status' => 'no_license',
                'message' => 'Cloud optimization requires a valid authentication token',
                'remaining' => 0,
                'tier' => $tier,
                'upgrade_url' => 'https://prorank.io/pricing',
            ];
        }

        $period = $this->resolve_quota_period($license_status, $is_free_tier && $cloud_opt_in, $cloud_opt_in_at);
        $usage_cache_key = $this->get_usage_cache_key($period['start'], $period['end']);
        $cached_usage = get_transient($usage_cache_key);
        $api_data = null;

        if ($cached_usage === false) {
            $api_data = $this->check_api_quota($period);
            if (is_array($api_data)) {
                if (isset($api_data['used'])) {
                    $cached_usage = (int) $api_data['used'];
                } elseif (isset($api_data['remaining'])) {
                    $cached_usage = max(0, $quota_total - max(0, (int) $api_data['remaining']));
                }

                if ($cached_usage !== false) {
                    set_transient($usage_cache_key, (int) $cached_usage, HOUR_IN_SECONDS);
                }
            }
        }

        $used = (int) ($cached_usage ?? 0);
        $remaining = max(0, $quota_total - $used);
        if (is_array($api_data)) {
            if (isset($api_data['remaining'])) {
                $remaining = max(0, (int) $api_data['remaining']);
            } elseif (isset($api_data['credits'])) {
                $remaining = max(0, (int) $api_data['credits']);
            }
        }

        $quota_yearly = $quota_total;
        $quota_monthly = (int) max(1, round($quota_total / 12));
        if (is_array($api_data)) {
            if (!empty($api_data['yearly_quota'])) {
                $quota_yearly = max(0, (int) $api_data['yearly_quota']);
            }

            if (!empty($api_data['monthly_quota'])) {
                $quota_monthly = max(0, (int) $api_data['monthly_quota']);
            }
        }

        $percentage_base = $quota_monthly > 0 ? $quota_monthly : $quota_yearly;
        $percentage = $percentage_base > 0 ? min(100, ($used / $percentage_base) * 100) : 0;

        $quota = [
            'yearly' => $quota_yearly,
            'period_start' => $period['start'],
            'period_end' => $period['end'],
            'period_source' => $period['source'],
        ];

        if ($quota_monthly > 0) {
            $quota['monthly'] = $quota_monthly;
        }

        $quota_formatted = [
            'yearly' => self::format_number($quota_yearly),
            'monthly' => self::format_number($quota_monthly),
        ];

        $next_reset = gmdate('c', $period['end']);
        $next_reset_display = $next_reset;
        $next_reset_timestamp = strtotime($next_reset);
        if ($next_reset_timestamp) {
            $next_reset_display = wp_date(
                get_option('date_format') . ' ' . get_option('time_format'),
                $next_reset_timestamp
            );
        }

        $message = __('Cloud optimization is available.', 'prorank-seo');
        if ($remaining <= 0) {
            if ($quota_monthly > 0) {
                $message = sprintf(
                    __('Cloud quota is exhausted for the current billing period. %1$s of %2$s images have been used. Next reset: %3$s.', 'prorank-seo'),
                    self::format_number($used),
                    self::format_number($quota_monthly),
                    $next_reset_display
                );
            } else {
                $message = sprintf(
                    __('Cloud quota is exhausted for the current billing period. Next reset: %s.', 'prorank-seo'),
                    $next_reset_display
                );
            }
        }

        $status = [
            'status' => $remaining > 0 ? 'active' : 'exhausted',
            'message' => $message,
            'tier' => $tier,
            'quota' => $quota,
            'quota_formatted' => $quota_formatted,
            'used' => $used,
            'used_formatted' => self::format_number($used),
            'remaining' => $remaining,
            'remaining_formatted' => self::format_number($remaining),
            'percentage' => $percentage,
            'next_reset' => $next_reset,
            'opt_in' => $cloud_opt_in,
        ];

        if (is_array($api_data) && isset($api_data['credits'])) {
            $status['credits'] = (int) $api_data['credits'];
            $status['credits_formatted'] = self::format_number((int) $api_data['credits']);
        }

        return $status;
    }

    /**
     * Check API quota status
     *
     * @return array|false API response or false on error
     */
    public function check_api_quota(array $period = null) {
        return false;
    }

    /**
     * Invalidate cached quota usage for the active billing period.
     *
     * @return void
     */
    private function invalidate_quota_usage_cache(): void {
        return;
    }
    
    /**
     * Format quota display
     *
     * @param int $number Number to format
     * @return string Formatted number
     */
    public static function format_number($number): string {
        if ($number >= 1000000) {
            return number_format($number / 1000000, 1) . 'M';
        } elseif ($number >= 1000) {
            return number_format($number / 1000, 1) . 'k';
        }
        return number_format($number);
    }
}
