<?php
/**
 * Cloudflare Handler for image resizing operations
 * 
 * Uses Cloudflare's Image Resizing service to generate sizes.
 * 
 * @package suspended-cloud/cloudflare-media-offload
 * @since 2.0.0
 */

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

class SQMedia_Cloudflare_Handler {
    
    /**
     * Cloudflare domain
     */
    private $domain;
    
    /**
     * Last error message
     */
    private $last_error = '';
    
    /**
     * Constructor
     */
    public function __construct() {
        $this->domain = SQMedia_Settings::get_cloudflare_domain();
    }
    
    /**
     * Check if Cloudflare Image Resizing is configured and enabled
     *
     * Returns true only when both a domain is set AND the resize engine
     * is set to "cloudflare" in settings. Use has_domain() for checks
     * that only need a domain (e.g., connection testing).
     */
    public function is_configured(): bool {
        return !empty($this->domain) && SQMedia_Settings::is_cloudflare_enabled();
    }

    /**
     * Check if a Cloudflare domain is set (regardless of whether resizing is enabled)
     */
    public function has_domain(): bool {
        return !empty($this->domain);
    }
    
    /**
     * Get last error
     */
    public function get_last_error(): string {
        return $this->last_error;
    }
    
    /**
     * Build Cloudflare Image Resize URL
     * 
     * @param string $source_url Full URL to source image
     * @param array $params Resize parameters
     * @return string Cloudflare resize URL
     */
    public function build_resize_url(string $source_url, array $params = []): string {
        if (!$this->is_configured()) {
            return $source_url;
        }
        
        // Default parameters
        $defaults = [
            'format' => 'auto',
            'fit' => 'scale-down',
            'quality' => 85,
        ];
        
        $params = array_merge($defaults, $params);
        
        // Build params string
        $param_parts = [];
        foreach ($params as $key => $value) {
            if ($value !== null && $value !== '') {
                $param_parts[] = $key . '=' . $value;
            }
        }
        $param_string = implode(',', $param_parts);
        
        // CRITICAL: Cloudflare Image Resizing needs the FULL source URL after the parameters
        // Format: https://domain.com/cdn-cgi/image/params/https://bucket.domain.com/path/image.jpg
        // OR: https://domain.com/cdn-cgi/image/params/path/image.jpg (if bucket is on same domain)
        
        // Check if source URL is on the same domain as Cloudflare
        $parsed_source = wp_parse_url($source_url);
        $source_host = $parsed_source['host'] ?? '';
        
        // If source is on the same domain, we can use just the path
        // Otherwise, we need the full URL
        if ($source_host === $this->domain) {
            $path = $parsed_source['path'] ?? '';
            $resize_url = 'https://' . $this->domain . '/cdn-cgi/image/' . $param_string . $path;
        } else {
            // Full URL as the source (Cloudflare will fetch from this URL)
            $resize_url = 'https://' . $this->domain . '/cdn-cgi/image/' . $param_string . '/' . $source_url;
        }
        
        return $resize_url;
    }
    
    /**
     * Generate a resized image via Cloudflare
     * 
     * This fetches the resized image from Cloudflare and returns the content.
     * 
     * @param string $source_url URL to source image in bucket
     * @param int $width Target width
     * @param int $height Target height
     * @param bool $crop Whether to crop
     * @return array|null Result with content and info, or null on failure
     */
    public function generate_size(string $source_url, int $width, int $height, bool $crop = false): ?array {
        if (!$this->is_configured()) {
            $this->last_error = __('Cloudflare not configured', 'staticq-media');
            return null;
        }
        
        // Determine output format from source URL extension
        // This ensures the resized file matches the original format
        $format = $this->get_cloudflare_format_from_url($source_url);
        
        // Build resize parameters — omit 0-valued dimensions so Cloudflare
        // auto-calculates proportionally instead of treating as "no constraint"
        $params = [
            'fit' => $crop ? 'cover' : 'scale-down',
            'quality' => 85,
        ];
        if ($width > 0) $params['width'] = $width;
        if ($height > 0) $params['height'] = $height;
        
        // Only add format if we have a supported one (otherwise Cloudflare preserves original)
        if ($format) {
            $params['format'] = $format;
        }
        
        $resize_url = $this->build_resize_url($source_url, $params);

        // Set Accept header to match requested format (prevents CF Polish from
        // auto-converting to WebP when we want JPEG).
        $accept = $format ? ('image/' . ($format === 'jpeg' ? 'jpeg' : $format) . ',image/*,*/*') : 'image/*,*/*';

        // Fetch the resized image
        $response = wp_remote_get($resize_url, [
            'timeout' => 30,
            'sslverify' => true,
            'headers' => ['Accept' => $accept],
        ]);

        if (is_wp_error($response)) {
            $this->last_error = $response->get_error_message();
            sqmedia()->log('Cloudflare resize failed: ' . $this->last_error, 'error', [
                'url' => $resize_url,
            ]);
            return null;
        }
        
        $code = wp_remote_retrieve_response_code($response);
        
        if ($code !== 200) {
            /* translators: %d: HTTP response status code */
            $this->last_error = sprintf(__('HTTP error %d', 'staticq-media'), $code);
            sqmedia()->log('Cloudflare resize HTTP error', 'error', [
                'url' => $resize_url,
                'code' => $code,
            ]);
            return null;
        }
        
        $content = wp_remote_retrieve_body($response);
        $content_type = wp_remote_retrieve_header($response, 'content-type');
        
        // Determine actual dimensions (may differ due to aspect ratio)
        // Never fall back to input params — they may contain 0 for unconstrained dims
        $image_info = @getimagesizefromstring($content);
        if (!$image_info || empty($image_info[0]) || empty($image_info[1])) {
            $this->last_error = __('Could not determine output dimensions', 'staticq-media');
            sqmedia()->log('Cloudflare resize: getimagesizefromstring failed', 'error');
            return null;
        }
        $actual_width = $image_info[0];
        $actual_height = $image_info[1];
        
        return [
            'content' => $content,
            'content_type' => $content_type,
            'width' => $actual_width,
            'height' => $actual_height,
        ];
    }
    
    /**
     * Generate WebP version via Cloudflare
     */
    public function generate_webp(string $source_url, int $quality = null): ?array {
        if (!$this->is_configured()) {
            $this->last_error = __('Cloudflare not configured', 'staticq-media');
            return null;
        }
        
        $quality = $quality ?? SQMedia_Settings::get_webp_quality();
        
        $params = [
            'format' => 'webp',
            'quality' => $quality,
        ];
        
        $resize_url = $this->build_resize_url($source_url, $params);

        $response = wp_remote_get($resize_url, [
            'timeout' => 30,
            'sslverify' => true,
            'headers' => ['Accept' => 'image/webp,image/*,*/*'],
        ]);

        if (is_wp_error($response)) {
            $this->last_error = $response->get_error_message();
            return null;
        }

        $code = wp_remote_retrieve_response_code($response);

        if ($code !== 200) {
            /* translators: %d: HTTP response status code */
            $this->last_error = sprintf(__('HTTP error %d', 'staticq-media'), $code);
            return null;
        }

        $body = wp_remote_retrieve_body($response);
        $ct = wp_remote_retrieve_header($response, 'content-type');

        // Verify CF actually returned WebP (it may return JPEG if WebP conversion is unsupported)
        if ($ct && strpos($ct, 'image/webp') === false) {
            $this->last_error = sprintf(
                /* translators: %s: content-type header value from Cloudflare response */
                __('Cloudflare returned %s instead of WebP', 'staticq-media'),
                $ct
            );
            return null;
        }

        return [
            'content' => $body,
            'content_type' => 'image/webp',
        ];
    }

    /**
     * Generate WebP version at specific dimensions via Cloudflare
     * 
     * @param string $source_url Source image URL
     * @param int $width Target width
     * @param int $height Target height
     * @param bool $crop Whether to crop
     * @param int|null $quality WebP quality
     * @return array|null Result with content and dimensions
     */
    public function generate_webp_size(string $source_url, int $width, int $height, bool $crop = false, int $quality = null): ?array {
        if (!$this->is_configured()) {
            $this->last_error = __('Cloudflare not configured', 'staticq-media');
            return null;
        }
        
        $quality = $quality ?? SQMedia_Settings::get_webp_quality();
        
        $params = [
            'width' => $width,
            'height' => $height,
            'fit' => $crop ? 'cover' : 'scale-down',
            'format' => 'webp',
            'quality' => $quality,
        ];
        
        $resize_url = $this->build_resize_url($source_url, $params);

        $response = wp_remote_get($resize_url, [
            'timeout' => 30,
            'sslverify' => true,
            'headers' => ['Accept' => 'image/webp,image/*,*/*'],
        ]);

        if (is_wp_error($response)) {
            $this->last_error = $response->get_error_message();
            return null;
        }

        $code = wp_remote_retrieve_response_code($response);

        if ($code !== 200) {
            /* translators: %d: HTTP response status code */
            $this->last_error = sprintf(__('HTTP error %d', 'staticq-media'), $code);
            return null;
        }

        $content = wp_remote_retrieve_body($response);
        $ct = wp_remote_retrieve_header($response, 'content-type');

        // Get actual dimensions from the generated WebP
        $image_info = @getimagesizefromstring($content);
        $actual_width = $image_info[0] ?? $width;
        $actual_height = $image_info[1] ?? $height;

        // Verify CF actually returned WebP
        if ($ct && strpos($ct, 'image/webp') === false) {
            $this->last_error = sprintf(
                /* translators: %s: content-type header value from Cloudflare response */
                __('Cloudflare returned %s instead of WebP', 'staticq-media'),
                $ct
            );
            return null;
        }

        return [
            'content' => $content,
            'content_type' => 'image/webp',
            'width' => $actual_width,
            'height' => $actual_height,
        ];
    }
    
    /**
     * Generate resized image and save to bucket
     * 
     * @param string $source_bucket_path Source file path in bucket
     * @param string $dest_bucket_path Destination path in bucket
     * @param int $width Target width
     * @param int $height Target height
     * @param bool $crop Whether to crop
     * @return array Result
     */
    public function generate_and_save(
        string $source_bucket_path,
        string $dest_bucket_path,
        int $width,
        int $height,
        bool $crop = false
    ): array {
        // Build source path - the relative path within wp-content/uploads
        $bucket_prefix = SQMedia_Settings::get_current_prefix();
        $relative_path = str_replace($bucket_prefix . '/', '', $source_bucket_path);
        $relative_path = ltrim($relative_path, '/');
        
        // Build the actual path as it would appear on the site
        $actual_path = '/wp-content/uploads/' . $relative_path;
        
        // Build source URL using Cloudflare domain
        $source_url = 'https://' . $this->domain . $actual_path;
        
        // Generate resized image
        $result = $this->generate_size($source_url, $width, $height, $crop);
        
        if (!$result) {
            return [
                'success' => false,
                'error' => $this->last_error,
            ];
        }
        
        // Upload to bucket
        $s3 = sqmedia()->get_storage_handler();
        $uploaded = $s3->upload_content($result['content'], $dest_bucket_path, [
            'mime_type' => $result['content_type'],
        ]);
        
        if (!$uploaded) {
            return [
                'success' => false,
                'error' => $s3->get_last_error(),
            ];
        }
        
        return [
            'success' => true,
            'path' => $dest_bucket_path,
            'width' => $result['width'],
            'height' => $result['height'],
            'content_type' => $result['content_type'],
        ];
    }
    
    /**
     * Generate WebP and save to bucket
     */
    /**
     * Generate WebP and save to bucket
     * 
     * Cloudflare Image Resizing uses a relative path - Cloudflare's origin
     * configuration handles fetching from R2.
     * Format: https://cf-domain.com/cdn-cgi/image/params/wp-content/uploads/path/image.jpg
     * 
     * In development, uses the dev test URL instead of the actual path.
     * 
     * @param string $relative_path Relative path (e.g., "2025/12/file1-scaled.jpg")
     * @param string $dest_bucket_path Full bucket path for destination
     * @param int|null $quality WebP quality
     * @param int|null $width Target width (optional, for generating sized WebP from master)
     * @param int|null $height Target height (optional)
     * @param bool $crop Whether to crop
     * @return array Result with success status
     */
    /**
     * Resize an image via Cloudflare and save to bucket
     *
     * Uses Cloudflare Image Resizing to resize (and optionally convert) an image,
     * then uploads the result directly to the storage bucket.
     *
     * @param string $relative_path Source path relative to wp-content/uploads
     * @param string $dest_bucket_path Destination path in bucket
     * @param string|null $format Output format ('webp', 'jpeg', 'png', or null to keep original)
     * @param int|null $quality Image quality (1-100)
     * @param int|null $width Target width
     * @param int|null $height Target height
     * @param bool $crop Whether to crop
     * @return array Result with success/error
     */
    public function resize_and_save(
        string $relative_path,
        string $dest_bucket_path,
        ?string $format = null,
        int $quality = null,
        int $width = null,
        int $height = null,
        bool $crop = false
    ): array {
        // Resolve null format from destination extension so Cloudflare always gets an explicit directive
        if ($format === null) {
            $ext = strtolower(pathinfo($dest_bucket_path, PATHINFO_EXTENSION));
            if ($ext === 'jpg' || $ext === 'jpeg') {
                $format = 'jpeg';
            } elseif ($ext === 'png') {
                $format = 'png';
            } elseif ($ext === 'gif') {
                $format = 'gif';
            } elseif ($ext === 'webp') {
                $format = 'webp';
            }
        }

        $format_label = $format ?? 'original';

        // Build params for Cloudflare
        $params = [
            'quality' => $quality ?? SQMedia_Settings::get_quality_value(),
        ];

        if ($format !== null) {
            $params['format'] = $format;
        }

        // Add dimensions if specified
        if ($width !== null && $width > 0) {
            $params['width'] = $width;
        }
        if ($height !== null && $height > 0) {
            $params['height'] = $height;
        }
        if ($width || $height) {
            $params['fit'] = $crop ? 'cover' : 'scale-down';
        }

        // Build params string
        $param_parts = [];
        foreach ($params as $key => $value) {
            if ($value !== null && $value !== '') {
                $param_parts[] = $key . '=' . $value;
            }
        }
        $param_string = implode(',', $param_parts);

        $actual_path = '/wp-content/uploads/' . ltrim($relative_path, '/');

        // Build Cloudflare URL: https://cf-domain.com/cdn-cgi/image/params/wp-content/uploads/path/image.jpg
        $resize_url = 'https://' . $this->domain . '/cdn-cgi/image/' . $param_string . $actual_path;

        // Set Accept header matching the requested format so Cloudflare returns
        // the correct content-type (browsers send Accept: image/webp which is why
        // CF URLs work in a browser but not from wp_remote_get without it).
        $accept_header = 'image/*,*/*';
        if ($format === 'webp') {
            $accept_header = 'image/webp,image/*,*/*';
        } elseif ($format === 'jpeg') {
            $accept_header = 'image/jpeg,image/*,*/*';
        } elseif ($format === 'png') {
            $accept_header = 'image/png,image/*,*/*';
        }

        // Fetch from Cloudflare
        $response = wp_remote_get($resize_url, [
            'timeout' => 30,
            'sslverify' => true,
            'headers' => [
                'Accept' => $accept_header,
            ],
        ]);

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

        $code = wp_remote_retrieve_response_code($response);

        if ($code !== 200) {
            $body = wp_remote_retrieve_body($response);
            /* translators: 1: HTTP response status code, 2: error message body */
            return ['success' => false, 'error' => sprintf(__('HTTP error %1$d: %2$s', 'staticq-media'), $code, substr($body, 0, 100))];
        }

        $content = wp_remote_retrieve_body($response);

        if (empty($content)) {
            return ['success' => false, 'error' => __('Empty response from Cloudflare', 'staticq-media')];
        }

        // Detect actual content format from magic bytes
        $magic = substr($content, 0, 4);
        $actual_format = 'unknown';
        if ($magic === 'RIFF') {
            $actual_format = 'webp';
        } elseif (substr($content, 0, 2) === "\xFF\xD8") {
            $actual_format = 'jpeg';
        } elseif (substr($content, 0, 4) === "\x89PNG") {
            $actual_format = 'png';
        }

        // Verify Cloudflare returned the requested format
        $response_ct = wp_remote_retrieve_header($response, 'content-type');

        // Use actual content format (magic bytes) as the source of truth,
        // not the content-type header which CF may set incorrectly.
        if ($format === 'webp' && $actual_format !== 'webp') {
            return [
                'success' => false,
                /* translators: %s: detected format from response body */
                'error' => sprintf(__('Cloudflare returned %s instead of WebP', 'staticq-media'), $actual_format),
            ];
        }

        if ($format === 'jpeg' && $actual_format === 'webp') {
            return [
                'success' => false,
                /* translators: %s: detected format from response body */
                'error' => sprintf(__('Cloudflare returned %s instead of JPEG — check CF Polish/WebP settings', 'staticq-media'), $actual_format),
            ];
        }

        // Determine MIME type for upload based on actual content
        $mime_map = ['webp' => 'image/webp', 'jpeg' => 'image/jpeg', 'png' => 'image/png'];
        $mime_type = $mime_map[$actual_format] ?? ($response_ct ?: 'image/jpeg');


        // Upload to bucket
        $s3 = sqmedia()->get_storage_handler();
        $uploaded = $s3->upload_content($content, $dest_bucket_path, [
            'mime_type' => $mime_type,
        ]);

        if (!$uploaded) {
            return ['success' => false, 'error' => $s3->get_last_error()];
        }


        return [
            'success' => true,
            'path' => $dest_bucket_path,
            'content_type' => $mime_type,
        ];
    }

    /**
     * Generate a WebP file via Cloudflare and save to bucket (convenience wrapper)
     */
    public function generate_webp_and_save(
        string $relative_path,
        string $dest_bucket_path,
        int $quality = null,
        int $width = null,
        int $height = null,
        bool $crop = false
    ): array {
        return $this->resize_and_save($relative_path, $dest_bucket_path, 'webp', $quality, $width, $height, $crop);
    }
    
    /**
     * Build Cloudflare resize URL from a relative path
     * 
     * This uses the Cloudflare domain directly, not a bucket URL.
     * The path should be relative to wp-content/uploads.
     * 
     * @param string $relative_path Path like "2025/12/file1.jpg"
     * @param array $params Cloudflare image params
     * @return string Full Cloudflare Image Resizing URL
     */
    public function build_resize_url_from_path(string $relative_path, array $params = []): string {
        // Build params string
        $param_parts = [];
        foreach ($params as $key => $value) {
            if ($value !== null && $value !== '') {
                $param_parts[] = $key . '=' . $value;
            }
        }
        $param_string = implode(',', $param_parts);
        
        // Build the path as it would appear on the Cloudflare-proxied site
        // This assumes wp-content/uploads is the path structure
        $full_path = '/wp-content/uploads/' . ltrim($relative_path, '/');
        
        // Cloudflare URL format: https://domain.com/cdn-cgi/image/params/path
        $url = 'https://' . $this->domain . '/cdn-cgi/image/' . $param_string . $full_path;
        
        return $url;
    }
    
    /**
     * Test Cloudflare connection
     */
    public function test_connection(): array {
        if (!$this->has_domain()) {
            return [
                'success' => false,
                'error' => __('Cloudflare domain not configured', 'staticq-media'),
            ];
        }
        
        // Try to access the domain
        $test_url = 'https://' . $this->domain . '/cdn-cgi/trace';
        
        $response = wp_remote_get($test_url, [
            'timeout' => 10,
            'sslverify' => true,
        ]);
        
        if (is_wp_error($response)) {
            return [
                'success' => false,
                'error' => $response->get_error_message(),
            ];
        }
        
        $code = wp_remote_retrieve_response_code($response);
        
        if ($code !== 200) {
            return [
                'success' => false,
                /* translators: %d: HTTP response status code */
                'error' => sprintf(__('HTTP error %d', 'staticq-media'), $code),
            ];
        }

        return [
            'success' => true,
            'message' => sprintf(
                /* translators: %s: Cloudflare domain name */
                __('Successfully connected to Cloudflare domain: %s', 'staticq-media'),
                $this->domain
            ),
        ];
    }
    
    /**
     * Check if the current WordPress site's DNS is proxied through Cloudflare.
     * 
     * This is important because Cloudflare Image Resizing only works when 
     * requests pass through Cloudflare's network (orange cloud enabled in DNS).
     * 
     * @return array Result with 'proxied' boolean and details
     */
    public static function check_site_cloudflare_proxy(?string $domain = null): array {
        // Use provided domain or fall back to site URL
        if (!empty($domain)) {
            $test_url = 'https://' . preg_replace('#^https?://#', '', $domain) . '/';
        } else {
            $test_url = home_url('/');
        }
        
        // Make a HEAD request to check for Cloudflare headers
        $response = wp_remote_head($test_url, [
            'timeout' => 10,
            'sslverify' => false, // Allow self-signed certs in dev
            'redirection' => 0,   // Don't follow redirects
        ]);
        
        if (is_wp_error($response)) {
            return [
                'success' => false,
                'proxied' => false,
                'error' => $response->get_error_message(),
            ];
        }
        
        // Check for Cloudflare-specific headers
        $headers = wp_remote_retrieve_headers($response);
        
        // CF-Ray is always present when traffic goes through Cloudflare
        $cf_ray = $headers['cf-ray'] ?? null;
        $server = $headers['server'] ?? '';
        $cf_cache_status = $headers['cf-cache-status'] ?? null;
        
        $is_proxied = !empty($cf_ray);
        
        if ($is_proxied) {
            return [
                'success' => true,
                'proxied' => true,
                'cf_ray' => $cf_ray,
                'message' => sprintf(
                    /* translators: %s: domain name being tested */
                    __('%s is proxied through Cloudflare. Image resizing will work.', 'staticq-media'),
                    esc_html(wp_parse_url($test_url, PHP_URL_HOST))
                ),
            ];
        } else {
            // Not proxied - check if it might be a Cloudflare domain but DNS-only (grey cloud)
            return [
                'success' => true,
                'proxied' => false,
                'message' => sprintf(
                    /* translators: %s: domain name being tested */
                    __('%s is not proxied through Cloudflare. Go to Cloudflare Dashboard > DNS > Records, edit your A/CNAME record, and set Proxy status to "Proxied".', 'staticq-media'),
                    esc_html(wp_parse_url($test_url, PHP_URL_HOST))
                ),
            ];
        }
    }
    
    /**
     * Test both Cloudflare domain connectivity AND site proxy status
     * 
     * @return array Combined test results
     */
    public function test_full_configuration(): array {
        $results = [
            'domain_test' => $this->test_connection(),
            'proxy_test' => self::check_site_cloudflare_proxy(),
        ];
        
        // Overall success requires both tests to pass
        $results['success'] = $results['domain_test']['success'] && 
                              $results['proxy_test']['success'] && 
                              $results['proxy_test']['proxied'];
        
        if (!$results['success']) {
            $errors = [];
            if (!$results['domain_test']['success']) {
                $errors[] = $results['domain_test']['error'] ?? __('Domain test failed', 'staticq-media');
            }
            if (!$results['proxy_test']['proxied']) {
                $errors[] = $results['proxy_test']['message'] ?? __('Site not proxied through Cloudflare', 'staticq-media');
            }
            $results['error'] = implode(' ', $errors);
        } else {
            $results['message'] = __('Cloudflare is properly configured for image resizing.', 'staticq-media');
        }
        
        return $results;
    }
    
    /**
     * Get Cloudflare Image Resizing URL for display
     * 
     * This generates a URL that will be resized on-the-fly by Cloudflare
     * when accessed. Used for dynamic sizing in HTML.
     */
    public function get_resized_url(string $source_url, int $width, int $height = 0, array $options = []): string {
        $params = array_merge([
            'width' => $width,
            'fit' => 'scale-down',
            'format' => 'auto',
            'quality' => 85,
        ], $options);
        
        if ($height > 0) {
            $params['height'] = $height;
        }
        
        return $this->build_resize_url($source_url, $params);
    }
    
    /**
     * Get WebP URL for an image
     */
    public function get_webp_url(string $source_url): string {
        return $this->build_resize_url($source_url, [
            'format' => 'webp',
            'quality' => SQMedia_Settings::get_webp_quality(),
        ]);
    }
    
    /**
     * Get Cloudflare format parameter from source URL extension
     * 
     * Maps file extensions to Cloudflare Image Resizing format values.
     * Returns null for unsupported formats (Cloudflare will preserve original).
     * 
     * @param string $url Source URL
     * @return string|null Cloudflare format value or null
     */
    private function get_cloudflare_format_from_url(string $url): ?string {
        // Extract extension from URL (ignore query strings)
        $path = wp_parse_url($url, PHP_URL_PATH);
        $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
        
        // Map extensions to Cloudflare format values
        // See: https://developers.cloudflare.com/images/transform-images/transform-via-url/#format
        switch ($ext) {
            case 'jpg':
            case 'jpeg':
                return 'jpeg';
            case 'png':
                return 'png';
            case 'gif':
                return 'gif';
            case 'webp':
                return 'webp';
            case 'avif':
                return 'avif';
            default:
                // Unknown format - let Cloudflare preserve original
                return null;
        }
    }
}