<?php

namespace Lws\Classes\Images;

class LwsOptimizeImageOptimizationPro
{
    private $log_file;
    private $format = ['jpg', 'jpeg', 'jpe', 'png'];
    private $wp_filesystem;

    /**
     * Cached probe result for local AVIF support — extension_loaded + queryFormats.
     * @var bool|null
     */
    private static $local_avif_supported = null;

    /**
     * Returns true if Imagick is installed AND ImageMagick was built with AVIF support.
     * AVIF requires ImageMagick >= 7.0.25 + libheif (Imagick >= 3.7 bound).
     *
     * Cached static so we only probe Imagick::queryFormats() once per request.
     */
    public static function lwsop_local_supports_avif()
    {
        if (self::$local_avif_supported !== null) {
            return self::$local_avif_supported;
        }
        if (!extension_loaded('imagick') || !class_exists('Imagick')) {
            return self::$local_avif_supported = false;
        }
        try {
            $probe = new \Imagick();
            $formats = $probe->queryFormats();
            self::$local_avif_supported = in_array('AVIF', $formats, true);
        } catch (\Exception $e) {
            self::$local_avif_supported = false;
        }
        return self::$local_avif_supported;
    }

    public function __construct()
    {
        // Initialize WP_Filesystem
        $this->init_wp_filesystem();

        $state = get_option('lws_optimize_deactivate_temporarily');

        // Create log file in uploads directory if it doesn't exist
        $upload_dir = wp_upload_dir();
        $log_dir = $upload_dir['basedir'] . '/lwsoptimize';
        if (!$this->wp_filesystem->exists($log_dir)) {
            wp_mkdir_p($log_dir);
        }
        $this->log_file = $log_dir . '/debug.log';
        if (!$this->wp_filesystem->exists($this->log_file)) {
            $this->wp_filesystem->put_contents($this->log_file, '', FS_CHMOD_FILE);
        }

        // Refresh all informations on the conversion
        add_action('wp_ajax_lws_optimize_image_conversion_data_fetch', [$this, 'lws_optimize_refresh_conversion_data']);

        // Start the conversion cron (using the API)
        add_action('wp_ajax_lws_optimize_start_conversion_api', [$this, 'lws_optimize_start_conversion_api']);

        // Start the conversion cron (using Imagick)
        add_action('wp_ajax_lws_optimize_start_conversion_standard', [$this, 'lws_optimize_start_conversion_standard']);

        // Start the autoconversion (using the API)
        add_action('wp_ajax_lws_optimize_start_autoconversion_api', [$this, 'lws_optimize_start_autoconversion_api']);

        // Start the autoconversion (using Imagick)
        add_action('wp_ajax_lws_optimize_start_autoconversion_standard', [$this, 'lws_optimize_start_autoconversion_standard']);

        // Start the deconversion cron
        add_action('wp_ajax_lws_optimize_start_deconversion', [$this, 'lws_optimize_start_deconversion']);

        // Deactivate all crons
        add_action('wp_ajax_lws_optimize_stop_all_conversions', [$this, 'lws_optimize_stop_all_conversions']);

        if (!$state) {
            // Get the autoconversion options from the DB (if any)
            $autoconversion_options = get_option('lws_optimize_image_autoconversion_options', []);

            // Launch hook to autoconvert on upload images
            if (isset($autoconversion_options['state']) && $autoconversion_options['state']) {
                add_filter('wp_handle_upload_prefilter', [$this, 'lws_optimize_autoupload_images']);
            }


            // Replace images in the content
            add_filter('the_content', [$this, 'lws_optimize_replace_images_api']);
            add_filter('wp_filter_content_tags', [$this, 'lws_optimize_replace_images_api']);
            add_filter('post_thumbnail_html', [$this, 'lws_optimize_replace_images_api']);
            add_filter('widget_text_content', [$this, 'lws_optimize_replace_images_api']);
            add_filter('widget_custom_html_content', [$this, 'lws_optimize_replace_images_api']);
            add_action('template_redirect', [$this, 'lws_optimize_start_output_buffer']);

            // Cron to convert or deconvert images
            add_action('lws_optimize_pro_image_conversion_cron', [$this, 'lws_optimize_pro_image_conversion_cron']);
            add_action("lws_optimize_image_conversion_cron", [$this, "lws_optimize_image_conversion_cron"]);
            add_action('lws_optimize_image_deconversion_cron', [$this, 'lws_optimize_image_deconversion_cron']);
        }
    }

    /**
     * Initialize WP_Filesystem
     */
    private function init_wp_filesystem() {
        global $wp_filesystem;

        if (empty($wp_filesystem)) {
            require_once(ABSPATH . '/wp-admin/includes/file.php');
            WP_Filesystem();
        }

        $this->wp_filesystem = $wp_filesystem;
    }

    /**
     * Write to log file using WP_Filesystem
     */
    private function write_log($message) {
        if (!$this->wp_filesystem) {
            return;
        }

        $log_entry = '[' . gmdate('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;

        // Get existing content if file exists
        $existing_content = '';
        if ($this->wp_filesystem->exists($this->log_file)) {
            $existing_content = $this->wp_filesystem->get_contents($this->log_file);
        }

        // Append new content
        $this->wp_filesystem->put_contents($this->log_file, $existing_content . $log_entry, FS_CHMOD_FILE);
    }

    public function lws_optimize_fetch_conversion_data() {
        $standard_data = [
            // All standard informations
            'conversion_status' => false,
            'deconversion_status' => false,
            'autoconversion_status' => false,
            'next_conversion' => 0,
            'next_deconversion' => 0,
            'images_to_convert' => 0,
            'images_converted' => 0,
            'images_left_to_convert' => 0,
            'size_reduction' => 0,
            'images_listing' => [],
            'remaining_credits' => "-",
            'api_key' => "",

            // All informations used in the on-website conversion
            'images_per_run' => 30,
            'images_quality' => 'balanced',
            'images_size' => 2560,
        ];

        // Get the conversion options from the DB (if any) and merge with the standard data to make sure all basic info is there
        $conversion_options = get_option('lws_optimize_image_conversion_options', []);
        $conversion_options = array_merge($standard_data, $conversion_options);

        // Check if a conversion or deconversion is ongoing
        $is_conversion_ongoing = !empty($conversion_options['conversion_status']);
        $is_deconversion_ongoing = !empty($conversion_options['deconversion_status']);

        if ($is_conversion_ongoing || $is_deconversion_ongoing) {
            wp_die(json_encode(array('code' => 'SUCCESS', 'data' => $conversion_options), JSON_PRETTY_PRINT));
        } else {
            // Call refresh to get the latest data
            $this->lws_optimize_refresh_conversion_data();
            // lws_optimize_refresh_conversion_data() will wp_die itself
        }
    }


    /**
     * Refresh the images to be converted listing
     * and check the status of the conversion processes
     */
    public function lws_optimize_refresh_conversion_data() {
        // Format allowed to be converted
        $format = $this->format;

        $standard_data = [
            // All standard informations
            'conversion_status' => false,
            'deconversion_status' => false,
            'autoconversion_status' => false,
            'next_conversion' => 0,
            'next_deconversion' => 0,
            'images_to_convert' => 0,
            'images_converted' => 0,
            'images_left_to_convert' => 0,
            'size_reduction' => 0,
            'images_listing' => [],
            'remaining_credits' => "-",
            'api_key' => "",

            // All informations used in the on-website conversion
            'images_per_run' => 30,
            'images_quality' => 'balanced',
            'images_size' => 2560,
        ];

        // Get the conversion options from the DB (if any) and merge with the standard data to make sure all basic info is there
        $conversion_options = get_option('lws_optimize_image_conversion_options', []);
        $conversion_options = array_merge($standard_data, $conversion_options);

        $images_listing = $conversion_options['images_listing'] ?? [];

        // Get all images from the media library with pagination to reduce memory usage
        $args = array(
            'post_type'      => 'attachment',
            'post_mime_type' => 'image',
            'post_status'    => 'inherit',
            'posts_per_page' => 200, // Process in chunks to avoid memory issues
            'paged'          => 1,
        );

        $images = [];
        $has_more = true;

        while ($has_more) {
            // Use global namespace for WordPress core classes
            $query = new \WP_Query($args);

            if (!empty($query->posts)) {
                $images = array_merge($images, $query->posts);
                $args['paged']++;
            } else {
                $has_more = false;
            }

            // Free memory
            wp_reset_postdata();
        }

        foreach ($images as $image) {
            $id = $image->ID;
            $file_path = get_attached_file($id);

            // Fix potential duplicate path segments in file path
            if (strpos($file_path, '/wp-content/uploads/wp-content/uploads/') !== false) {
                $file_path = preg_replace('|(.*?/wp-content/uploads)/wp-content/uploads/|', '$1/', $file_path);
                update_post_meta($id, '_wp_attached_file', str_replace(ABSPATH, '', $file_path));
            }

            // Get file extension and path info
            $path_info = pathinfo($file_path);
            $extension = strtolower($path_info['extension'] ?? '');

            // Check if the filename contains multiple _lwsoptimized suffixes and fix it
            if (preg_match('/_lwsoptimized(_lwsoptimized)+/', $path_info['filename'])) {
                // Fix the filename to have only one _lwsoptimized suffix
                $base_filename = preg_replace('/_lwsoptimized(_lwsoptimized)+/', '', $path_info['filename']);
                $new_filename = $base_filename . '_lwsoptimized';
                $new_file_path = $path_info['dirname'] . '/' . $new_filename . '.' . $extension;

                // Rename the file if it exists
                if ($this->wp_filesystem->exists($file_path)) {
                    $this->wp_filesystem->move($file_path, $new_file_path);
                    // Update the attachment metadata
                    update_post_meta($id, '_wp_attached_file', str_replace(ABSPATH, '', $new_file_path));
                    $file_path = $new_file_path;
                    $path_info['filename'] = $new_filename;
                }
            }

            // Check the file extension against the allowed formats array
            // If the extension is in there, then we have to convert it
            $to_convert = in_array($extension, $format);

            if ($to_convert) {
                // Create path for the optimized version with _lwsoptimized suffix
                $webp_path = $path_info['dirname'] . '/' . $path_info['filename'] . '_lwsoptimized.webp';
                $avif_path = $path_info['dirname'] . '/' . $path_info['filename'] . '_lwsoptimized.avif';

                // If WordPress recognizes the file as NOT WebP/AVIF
                // but the files somehow exist, we delete them
                // as it may means the conversion failed
                if ($this->wp_filesystem->exists($webp_path)) {
                    if ($extension == "webp" && isset($images_listing[$id])) {
                        if ($images_listing[$id]['format'] == 'webp') {
                            $images_listing[$id]['converted'] = true;
                            continue;
                        }
                    }
                    $this->wp_filesystem->delete($webp_path);
                }
                if ($this->wp_filesystem->exists($avif_path)) {
                    $this->wp_filesystem->delete($avif_path);
                }

                if (!$this->wp_filesystem->exists($file_path)) {
                    // If the file does not exist, we skip it
                    continue;
                }


                // Add the image to the listing
                $images_listing[$id] = [
                    'name' => $image->post_title,
                    'path' => $file_path,
                    'format' => $extension,
                    'size' => $this->wp_filesystem->size($file_path),
                    'converted' => false,
                ];
            }
            // If the file is in WebP or AVIF, we need to check if it was converted with our plugin
            // by checking the filename for the _lwsoptimized suffix
            else if (in_array($extension, ['webp', 'avif'])) {
                if (preg_match('/_lwsoptimized$/', $path_info['filename'])) {
                    // If the image is in our array already
                    if (!empty($images_listing[$id])) {
                        // If the file does not exist but there is an original, we consider it not converted
                        if (!$this->wp_filesystem->exists($file_path)) {
                            $images_listing[$id]['path'] = str_replace('_lwsoptimized', '', $images_listing[$id]['path']);
                            if ($this->wp_filesystem->exists($images_listing[$id]['path'])) {
                                $images_listing[$id] = array_merge($images_listing[$id], [
                                    'converted' => false,
                                    'converted_path' => null,
                                    'converted_format' => null,
                                    'converted_size' => null,
                                    'compression' => 0,
                                ]);

                                continue;
                            } else {
                                unset($images_listing[$id]);
                                continue;
                            }
                        }

                        $original_size = $images_listing[$id]['size'] ?? 0;
                        $converted_size = $this->wp_filesystem->size($file_path);

                        // Update the array with fresh data about the converted image without overriding the informations about the original
                        $images_listing[$id] = array_merge($images_listing[$id], [
                            'converted' => true,
                            'converted_path' => $file_path,
                            'converted_format' => $extension,
                            'converted_size' => $converted_size,
                            'compression' => $converted_size > 0 ? round($converted_size / $original_size, 2) : 0,
                        ]);
                    }
                    else {
                        // Try to find the original image by removing _lwsoptimized suffix and testing different extensions
                        $original_filename = str_replace('_lwsoptimized', '', $path_info['filename']);
                        $original_found = false;
                        $original_path = '';
                        $original_extension = '';

                        // Check each possible format extension to find the original file
                        foreach ($format as $ext) {
                            $possible_original = $path_info['dirname'] . '/' . $original_filename . '.' . $ext;
                            if ($this->wp_filesystem->exists($possible_original)) {
                                $original_path = $possible_original;
                                $original_extension = $ext;
                                $original_found = true;
                                break;
                            }
                        }

                        // If the original has been found, we can store it in the array
                        if ($original_found) {
                            $original_size = $this->wp_filesystem->size($original_path);
                            $converted_size = $this->wp_filesystem->size($file_path);

                            // Add a new entry for this converted image
                            $images_listing[$id] = [
                                'name' => $image->post_title,
                                'path' => $original_path,
                                'format' => $original_extension,
                                'size' => $original_size,
                                'converted' => true,
                                'converted_path' => $file_path,
                                'converted_format' => $extension,
                                'converted_size' => $converted_size,
                                'compression' => $original_size > 0 ? round($converted_size / $original_size, 2) : 0,
                            ];
                        }

                    }
                } else {
                    if ($extension == 'webp') {
                        // Add the image to the listing
                        $images_listing[$id] = [
                            'name' => $image->post_title,
                            'path' => $file_path,
                            'format' => $extension,
                            'size' => $this->wp_filesystem->size($file_path),
                            'converted' => false,
                        ];
                    }
                }
            }
        }


        // Count images with 'converted' set to true and calculate size reduction
        $converted_count = 0;
        $total_compression = 0;

        $original_size = 0;
        $converted_size = 0;

        foreach ($images_listing as $image) {
            if (isset($image['converted']) && $image['converted'] === true) {
                $converted_count++;

                if (isset($image['compression'])) {
                    $total_compression += $image['compression'];
                }

                if (isset($image['converted_size']) && isset($image['size'])) {
                    $converted_size += $image['converted_size'];
                    $original_size += $image['size'];
                }
            }
        }

        $size_reduction_num = 0;
        if ($converted_count > 0) {
            // Calculate the size reduction percentage
            $size_reduction = round($total_compression / $converted_count, 2) * 100;
            $size_reduction_num = $original_size - $converted_size;
        } else {
            $size_reduction = 0;
        }


        // Update the conversion options with the new data
        $conversion_options['images_listing'] = $images_listing;
        $conversion_options['images_to_convert'] = count($images_listing);
        $conversion_options['images_converted'] = $converted_count;
        $conversion_options['images_left_to_convert'] = count($images_listing) - $converted_count;
        $conversion_options['size_reduction'] = $size_reduction;
        $conversion_options['size_reduction_num'] = $this->lwsOpSizeConvert($size_reduction_num);

        // Manage the different crons. Only one cron can be active at any given time and priority is given to the pro version

        // Check for scheduled tasks
        $standard_conversion = wp_next_scheduled("lws_optimize_image_conversion_cron");
        $pro_conversion = wp_next_scheduled("lws_optimize_pro_image_conversion_cron");
        $deconversion = wp_next_scheduled("lws_optimize_image_deconversion_cron");

        // Resolve conflicts between standard and pro versions
        if ($standard_conversion && $pro_conversion) {
            wp_unschedule_event($standard_conversion, "lws_optimize_image_conversion_cron");
            $standard_conversion = false;

            $this->write_log('Conflict : Both conversion crons activated at the same time. Both removed.');
        }


        // Resolve conflicts between conversion and deconversion
        if ($pro_conversion && $deconversion) {
            wp_unschedule_event($pro_conversion, "lws_optimize_pro_image_conversion_cron");
            wp_unschedule_event($deconversion, "lws_optimize_image_deconversion_cron");
            $deconversion = false;

            $this->write_log('Conflict : Both pro conversion and deconversion activated at the same time. Both removed.');
        }

        if ($standard_conversion && $deconversion) {
            wp_unschedule_event($standard_conversion, "lws_optimize_image_conversion_cron");
            wp_unschedule_event($deconversion, "lws_optimize_image_deconversion_cron");
            $standard_conversion = false;
            $deconversion = false;

            $this->write_log('Conflict : Both standard conversion and deconversion activated at the same time. Both removed.');
        }

        // Determine active processes
        $active_conversion = $standard_conversion ?: $pro_conversion;

        // Update options with current status
        $conversion_options['conversion_status'] = (bool)$active_conversion;
        $conversion_options['deconversion_status'] = (bool)$deconversion;
        $conversion_options['next_conversion'] = $active_conversion;
        $conversion_options['next_deconversion'] = $deconversion;

        // Get the state of the autoconversion
        $autoconversion_options = get_option('lws_optimize_image_autoconversion_options', []);
        (isset($autoconversion_options['state']) && $autoconversion_options['state']) ? $conversion_options['autoconversion_status'] = true : $conversion_options['autoconversion_status'] = false;

        $response = $this->get_remaining_credits();

        $result = json_decode($response, true);

        // Check for JSON decoding errors
        if (json_last_error() !== JSON_ERROR_NONE) {
            error_log(json_encode(['code' => 'JSON_ERROR', 'message' => 'Failed to decode JSON response: ' . json_last_error_msg(), 'data' => $response]));
        }

        // Check for API errors
        if (isset($result['code']) && $result['code'] == 'SUCCESS') {
            $conversion_options['remaining_credits'] = $result['data']['credits'] ?? 2000;
            $conversion_options['api_key'] = $result['data']['api_key'] ?? '';
        }

        // Save the updated options regardless of API response
        update_option('lws_optimize_image_conversion_options', $conversion_options);

        wp_die(json_encode(array('code' => 'SUCCESS', 'data' => $conversion_options), JSON_PRETTY_PRINT));

    }

    /**
     * Convert all images on the WordPress website using an external API
     */
    public function lws_optimize_start_conversion_api() {
        check_ajax_referer('nonce_for_lws_optimize_start_conversion_api', '_ajax_nonce');

        $this->write_log('Starting pro conversion (API)...');

        $scheduled = false;
        // Deactivate every cron beforehand...
        $standard_conversion = wp_next_scheduled("lws_optimize_image_conversion_cron");
        $pro_conversion = wp_next_scheduled("lws_optimize_pro_image_conversion_cron");
        $deconversion = wp_next_scheduled("lws_optimize_image_deconversion_cron");

        wp_unschedule_event($standard_conversion, "lws_optimize_image_conversion_cron");
        wp_unschedule_event($pro_conversion, "lws_optimize_pro_image_conversion_cron");
        wp_unschedule_event($deconversion, "lws_optimize_image_deconversion_cron");

        $this->write_log('Deactivating all conversion crons');

        delete_transient('lws_optimize_conversion_lock');

        $this->write_log('Conversion lock removed when starting pro conversion');

        // ...and then schedule the cron for the pro version
        $scheduled = wp_schedule_event(time() + 10, 'lws_minute', 'lws_optimize_pro_image_conversion_cron');
        $conversion_options = $this->lws_optimize_refresh_conversion_data();

        if ($scheduled) {
            $this->write_log('Pro conversion activated. Next run: ' . $scheduled);

            wp_die(json_encode(array('code' => 'SUCCESS', 'scheduled' => $scheduled, 'data' => $conversion_options, JSON_PRETTY_PRINT)));
        } else {
            $this->write_log('Failed to start pro conversion cron');

            wp_die(json_encode(array('code' => 'FAILURE', JSON_PRETTY_PRINT)));
        }
    }

    /**
     * Convert all images on the WordPress website using Imagick in PHP
     */
    public function lws_optimize_start_conversion_standard() {
        check_ajax_referer('nonce_for_lws_optimize_start_conversion_standard', '_ajax_nonce');

        $this->write_log('Starting standard conversion...');

        $quality = sanitize_text_field($_POST['quality'] ?? 'balanced');
        $size = intval($_POST['size'] ?? 2560);
        $images_per_run = intval($_POST['images_per_run']) ?? 30;

        // Get the conversion options from the DB (if any) and update the values for the standard convertion
        $conversion_options = get_option('lws_optimize_image_conversion_options', []);
        $conversion_options['images_quality'] = $quality;
        $conversion_options['images_size'] = $size;
        $conversion_options['images_per_run'] = $images_per_run;
        update_option('lws_optimize_image_conversion_options', $conversion_options);

        $scheduled = false;
        // Deactivate every cron beforehand...
        $standard_conversion = wp_next_scheduled("lws_optimize_image_conversion_cron");
        $pro_conversion = wp_next_scheduled("lws_optimize_pro_image_conversion_cron");
        $deconversion = wp_next_scheduled("lws_optimize_image_deconversion_cron");

        wp_unschedule_event($standard_conversion, "lws_optimize_image_conversion_cron");
        wp_unschedule_event($pro_conversion, "lws_optimize_pro_image_conversion_cron");
        wp_unschedule_event($deconversion, "lws_optimize_image_deconversion_cron");

        $this->write_log('Deactivating all conversion crons');

        delete_transient('lws_optimize_conversion_lock');

        $this->write_log('Conversion lock removed when starting standard conversion');

        // ...and then schedule the cron
        $scheduled = wp_schedule_event(time() + 10, 'lws_minute', 'lws_optimize_image_conversion_cron');
        $conversion_options = $this->lws_optimize_refresh_conversion_data();

        if ($scheduled) {
            $this->write_log('Standard conversion activated. Next run: ' . $scheduled);

            wp_die(json_encode(array('code' => 'SUCCESS', 'scheduled' => $scheduled, 'data' => $conversion_options, JSON_PRETTY_PRINT)));
        } else {
            $this->write_log('Failed to start standard conversion cron');

            wp_die(json_encode(array('code' => 'FAILURE', JSON_PRETTY_PRINT)));
        }
    }

    /**
     * Deconvert all images on the WordPress website that have been converted using this plugin
     */
    public function lws_optimize_start_deconversion() {
        check_ajax_referer('nonce_for_lws_optimize_start_deconversion', '_ajax_nonce');

        $this->write_log('Starting Image Deconversion cron...');

        $scheduled = false;
        // Deactivate every cron beforehand...
        $standard_conversion = wp_next_scheduled("lws_optimize_image_conversion_cron");
        $pro_conversion = wp_next_scheduled("lws_optimize_pro_image_conversion_cron");
        $deconversion = wp_next_scheduled("lws_optimize_image_deconversion_cron");

        wp_unschedule_event($standard_conversion, "lws_optimize_image_conversion_cron");
        wp_unschedule_event($pro_conversion, "lws_optimize_pro_image_conversion_cron");
        wp_unschedule_event($deconversion, "lws_optimize_image_deconversion_cron");

        $this->write_log('Deactivating all conversion crons');

        delete_transient('lws_optimize_conversion_lock');

        $this->write_log('Conversion lock removed when starting deconversion');

        // ...and then schedule the cron for the deconversion
        $scheduled = wp_schedule_event(time() + 10, 'lws_minute', 'lws_optimize_image_deconversion_cron');
        $conversion_options = $this->lws_optimize_refresh_conversion_data();

        if ($scheduled) {
            $this->write_log('Image deconversion activated. Next run: ' . $scheduled);

            wp_die(json_encode(array('code' => 'SUCCESS', 'scheduled' => $scheduled, 'data' => $conversion_options, JSON_PRETTY_PRINT)));
        } else {
            $this->write_log('Failed to start Image deconversion cron');

            wp_die(json_encode(array('code' => 'FAILURE', JSON_PRETTY_PRINT)));
        }
    }

    /**
     * Stop every ongoing conversion process.
     * As only one should be activated at any given time, the function can deactivate them all
     * without issues, which is simpler than 1 function per cron.
     */
    public function lws_optimize_stop_all_conversions() {
        check_ajax_referer('nonce_for_lws_optimize_stop_all_conversions', '_ajax_nonce');

        $this->write_log('Deactivating all conversion crons...');

        $standard_conversion = wp_next_scheduled("lws_optimize_image_conversion_cron");
        $pro_conversion = wp_next_scheduled("lws_optimize_pro_image_conversion_cron");
        $deconversion = wp_next_scheduled("lws_optimize_image_deconversion_cron");

        wp_unschedule_event($standard_conversion, "lws_optimize_image_conversion_cron");
        wp_unschedule_event($pro_conversion, "lws_optimize_pro_image_conversion_cron");
        wp_unschedule_event($deconversion, "lws_optimize_image_deconversion_cron");

        $this->write_log('All conversion crons deactivated');

        // Since we're stopping all conversions, we should clear the lock as well
        delete_transient('lws_optimize_conversion_lock');

        $this->write_log('Conversion lock removed when stopping all conversions');

        wp_die(json_encode(array('code' => 'SUCCESS', JSON_PRETTY_PRINT)));
    }


    /**
     * Cron job to convert images in the background using the API
     * It will only process a limited number of images at a time to avoid timeouts
     * and will schedule the next run if there are still images to convert.
     *
     * @return string JSON response with the number of images processed, or error message
     */
    public function lws_optimize_pro_image_conversion_cron() {

        // Check if another conversion process is already running
        $conversion_lock = get_transient('lws_optimize_conversion_lock');

        // If lock exists, check if it's stale (older than 10 minutes)
        if ($conversion_lock) {
            $lock_time = is_array($conversion_lock) ? ($conversion_lock['time'] ?? 0) : 0;
            $stale_threshold = 600; // 10 minutes in seconds

            if (time() - $lock_time < $stale_threshold) {
                $this->write_log('Cron already ongoing. Waiting to convert');

                // Process is already running and not stale, exit
                return;
            } else {
                // Lock is stale, log it and continue
                $this->write_log('Detected stale lock (created ' . (time() - $lock_time) . ' seconds ago). Overriding.');
            }
        }

        // Set a lock with timestamp that expires in 5 minutes (300 seconds)
        set_transient('lws_optimize_conversion_lock', ['time' => time()], 300);

        $this->write_log('Cron lock now in place');
        sleep(2);


        // Process up to 10 images per cron run to avoid timeouts
        $images_processed = 0;
        $max_images_per_run = 30;

        // Determine max_images_per_run based on PHP's max_execution_time
        $max_execution_time = ini_get('max_execution_time');
        // If max_execution_time is 0 (unlimited) or high, use a reasonable default
        if ($max_execution_time == 0 || $max_execution_time > 90) {
            $max_images_per_run = 30;
        } else {
            // Estimate approximately 5-6 seconds per image conversion
            // For 30 second timeout, process 5 images; for 60 seconds, process 10
            $max_images_per_run = max(1, min(30, floor($max_execution_time / 2)));
        }

        // The max amount of times the convert_image function can fail on a HTTP_ERROR before stopping the process
        $max_errors_allowed = 20;
        $current_errors = 0;

        // Get all format allowed to be converted
        $format = $this->format;

        $conversion_options = get_option('lws_optimize_image_conversion_options', []);

        $images_to_process = $conversion_options['images_listing'] ?? [];

        $unconverted_images = 0;
        // Check if there are any images that need conversion and are available
        foreach ($images_to_process as $image) {
            if (empty($image['converted']) || $image['converted'] === false) {
                $unconverted_images++;
            }
        }


        $this->write_log('Images to process using the API: ' . $unconverted_images);

        foreach ($images_to_process as $key => $image) {
            // Check if we have reached the maximum number of images to process
            if ($images_processed >= $max_images_per_run) {
                $this->write_log('Maximum reached. Stopping cron at ' . $max_images_per_run . ' images');

                break;
            }

            if ($current_errors >= $max_errors_allowed) {
                $this->write_log('Maximum errors reached. Stopping cron at ' . $max_errors_allowed . ' errors');

                break;
            }

            if ($image['unavailable'] ?? false) {
                // If the image is marked as unavailable, skip it
                continue;
            }

            // If there is no converted key, then consider the image as not converted
            if (empty($image['converted'])) {
                $image['converted'] = false;
            }

            // If the image is already converted...
            if ($image['converted']) {
                // Check if the converted file exists, if not mark it as unconverted
                if (!$this->wp_filesystem->exists($image['converted_path'])) {
                    $image['converted'] = false;

                    $this->write_log("Converted image [{$image['converted_path']}] not found, marking as unconverted");
                }
                continue;
            }

            // If the image has no PATH or does not exists, skip it and mark it as unavailable
            if ((empty($image['path']) || !$this->wp_filesystem->exists($image['path']))) {
                $this->write_log("Original image at [{$image['path']}] not found. No conversion can be done");

                $images_to_process[$key]['unavailable'] = true;
                continue;
            }

            try {
                $response = $this->convert_image($image['path'], null);
            } catch (\Exception $e) {
                $this->write_log("Failed to convert image [{$image['path']}]. Error: {$e->getMessage()}");

                $current_errors++;
                $images_to_process[$key]['unavailable'] = true;
                error_log(json_encode(['code' => 'CONVERSION_ERROR', 'message' => 'Error during image conversion: ' . $e->getMessage(), 'data' => $image]));
                continue;
            }

            $result = json_decode($response, true);

            // Check for JSON decoding errors
            if (json_last_error() !== JSON_ERROR_NONE) {
                $this->write_log("Failed to decode JSON after converting [{$image['path']}]. Error: [" . json_last_error_msg() ."]");

                $images_to_process[$key]['unavailable'] = true;
                error_log(json_encode(['code' => 'JSON_ERROR', 'message' => 'Failed to decode JSON response: ' . json_last_error_msg(), 'data' => $response]));
                continue;
            }

            // Check for API errors
            if (!isset($result['code']) || $result['code'] !== 'SUCCESS') {
                $images_to_process[$key]['unavailable'] = true;

                error_log($response);

                // Add a HTTP failure to the count. If the API returns HTTP_ERRORs, generally it will NEVER return SUCCESS
                if ($result['code'] == 'HTTP_ERROR') {
                    $current_errors++;
                }

                $this->write_log("Failed to convert image [{$image['path']}]." . PHP_EOL . "Error code: {$result['code']}");
                $this->write_log("Error message: {$result['message']}");
                $this->write_log("Error data: " . json_encode($result['data']));

                if ($result["code"] == "NO_CREDITS") {
                    // If there are no credits left, stop the conversion process
                    $conversion_records['status'] = false;

                    // Delete lock
                    delete_transient('lws_optimize_conversion_lock');

                    $this->write_log("Removing cron lock");

                    // Store the changes made to the images data
                    $conversion_options['images_listing'] = $images_to_process;
                    // Update the conversion options in the database
                    update_option('lws_optimize_image_conversion_options', $conversion_options);

                    return $response;
                }

                // 403 means Forbidden, AKA the APIKey is not valid. In that case, remove it from the options to get it anew
                if ($result['message'] == "403") {
                    delete_option('lws_optimize_image_api_key');
                    $this->write_log("API Key was not valid and has been removed. Correct APIKey will be retrieved next conversion.");
                }

                continue;
            }

            $converted_path = $result['data']['optimized_path'] ?? '';
            $converted_format = $result['data']['format'] ?? '';

            if (empty($converted_format)) {
                $this->write_log("Image [{$converted_path}] has no format returned. Conversion has failed and will not be attempted again");

                error_log(json_encode(['code' => 'MISSING_FORMAT', 'message' => 'No format returned', 'data' => $result]));
                $images_to_process[$key]['unavailable'] = true;
                continue;
            }

            $size = $image['size'];
            $converted_size = filesize($converted_path) ?? 0;
            $compression = $converted_size > 0 ? round($converted_size / $size, 2) : 0;

            $image['converted'] = true;
            $image['converted_path'] = $converted_path;
            $image['converted_format'] = $converted_format;
            $image['converted_size'] = $converted_size;
            $image['compression'] = $compression;

            // Only update the attachment if the new format is different from the original
            if ($converted_format !== $image['format']) {
                $attachment = array(
                    'ID' => $key,
                    'post_mime_type' => 'image/' . $converted_format,
                );
                wp_update_post($attachment);
            }

            // Update the attachment with the new mime type
            // Delete old attachment metadata
            delete_post_meta($key, '_wp_attachment_metadata');

            // Update with new metadata
            wp_update_attachment_metadata($key, wp_generate_attachment_metadata($key, $result['data']['optimized_path']));
            update_post_meta($key, '_wp_attached_file', str_replace(ABSPATH, '', $result['data']['optimized_path']));

            $this->write_log("Image [{$converted_path}] has been updated in WordPress. Conversion successful");

            // Regenerate the image thumbnails for the optimized image
            if (function_exists('wp_generate_attachment_metadata')) {
                $metadata = wp_generate_attachment_metadata($key, $result['data']['optimized_path']);
                wp_update_attachment_metadata($key, $metadata);

                $this->write_log("Image [{$converted_path}]'s thumbnails are being regenerated");
            }

            // Store the changes to the images
            $images_to_process[$key] = $image;

            // Increment the processed images count
            $images_processed++;
        }

        // If no images were processed in this run and there are no images left to convert
        // or if all remaining images are unavailable, stop the cron
        if ($images_processed == 0) {
            $all_unavailable = true;
            $unconverted_images = 0;

            // Check if there are any images that need conversion and are available
            foreach ($images_to_process as $image) {
                if (empty($image['converted']) || $image['converted'] === false) {
                    $unconverted_images++;
                    // If at least one image is not marked as unavailable, we still have work to do
                    if (empty($image['unavailable']) || $image['unavailable'] === false) {
                        $all_unavailable = false;
                        break;
                    }
                }
            }

            // If there are no unconverted images or all remaining ones are unavailable
            if ($unconverted_images == 0 || $all_unavailable) {
                // Unschedule the cron
                $next_conversion = wp_next_scheduled("lws_optimize_pro_image_conversion_cron");
                if ($next_conversion) {
                    wp_unschedule_event($next_conversion, "lws_optimize_pro_image_conversion_cron");

                    $this->write_log('No more images to convert or all remaining images are unavailable. Stopping cron.');

                    // Update the conversion status
                    $conversion_options['conversion_status'] = false;
                    $conversion_options['next_conversion'] = 0;
                }
            }
        }

        // Store the changes made to the images data
        $conversion_options['images_listing'] = $images_to_process;

        // Update the conversion options in the database
        update_option('lws_optimize_image_conversion_options', $conversion_options);

        // Delete lock for next cron
        delete_transient('lws_optimize_conversion_lock');

        $this->write_log('Cron lock removed. Data updated');

        // Return the $conversion_options and the amount of processed images
        return json_encode(array('code' => 'SUCCESS', 'data' => array('options' => $conversion_options, 'processed' => $images_processed), JSON_PRETTY_PRINT));
    }

    /**
     * Cron job to convert images in the background using Imagick
     * It will only process a limited number of images at a time to avoid timeouts
     * and will schedule the next run if there are still images to convert.
     *
     * @return string JSON response with the number of images processed
     */
    public function lws_optimize_image_conversion_cron()
    {
        // Check if another conversion process is already running
        $conversion_lock = get_transient('lws_optimize_conversion_lock');

        // If lock exists, check if it's stale (older than 10 minutes)
        if ($conversion_lock) {
            $lock_time = is_array($conversion_lock) ? ($conversion_lock['time'] ?? 0) : 0;
            $stale_threshold = 600; // 10 minutes in seconds

            if (time() - $lock_time < $stale_threshold) {
                $this->write_log('Cron already ongoing. Waiting to convert');

                // Process is already running and not stale, exit
                return json_encode(['code' => 'RUNNING', 'message' => 'Conversion process already running']);
            } else {
                // Lock is stale, log it and continue
                $this->write_log('Detected stale lock (created ' . (time() - $lock_time) . ' seconds ago). Overriding.');
            }
        }

        // Set a lock with timestamp that expires in 5 minutes (300 seconds)
        set_transient('lws_optimize_conversion_lock', ['time' => time()], 300);

        $this->write_log('Cron lock now in place');

        // Process up to the specified number of images per cron run
        $images_processed = 0;

        // Get conversion options and image listing
        $conversion_options = get_option('lws_optimize_image_conversion_options', []);
        $images_to_process = $conversion_options['images_listing'] ?? [];

        // Get the defined quality and size
        $quality = $conversion_options['images_quality'] ?? 'balanced';
        $max_size = $conversion_options['images_size'] ?? 2560;

        // Determine max_images_per_run based on PHP's max_execution_time
        $max_execution_time = ini_get('max_execution_time');
        // If max_execution_time is 0 (unlimited) or high, use a reasonable default
        if ($max_execution_time == 0 || $max_execution_time > 90) {
            $max_images_per_run = 10;
        } else {
            // Estimate approximately 5-6 seconds per image conversion
            // For 30 second timeout, process 5 images; for 60 seconds, process 10
            $max_images_per_run = max(1, min(10, floor($max_execution_time / 6)));
        }
        //$max_images_per_run = $conversion_options['images_per_run'] ?? 30;



        // Check if there are any images that need conversion and are available
        $unconverted_images = 0;
        foreach ($images_to_process as $image) {
            if (empty($image['converted']) || $image['converted'] === false) {
                $unconverted_images++;
            }
        }


        $this->write_log('Images to process using standard method: ' . $unconverted_images);

        foreach ($images_to_process as $key => $image) {
            // Check if we have reached the maximum number of images to process
            if ($images_processed >= $max_images_per_run) {
                $this->write_log('Maximum reached. Stopping cron at ' . $max_images_per_run . ' images');

                break;
            }

            if ($image['unavailable'] ?? false) {
                // If the image is marked as unavailable, skip it
                continue;
            }


            // If there is no converted key, then consider the image as not converted
            if (empty($image['converted'])) {
                $image['converted'] = false;
            }

            // If the image is already converted...
            if ($image['converted']) {
                // ... but the converted image cannot be found, convert it again
                if (empty($image['converted_path']) || !file_exists($image['converted_path'])) {
                    $this->write_log("Image at [{$image['converted_path']}] does not exist. Converting again");

                    $image['converted'] = false;
                } else {
                    // ... otherwise skip it
                    continue;
                }
            }

            // If the image has no PATH or does not exists, skip it and mark it as unavailable
            if ((empty($image['path']) || !$this->wp_filesystem->exists($image['path']))) {
                $this->write_log("Original image at [{$image['path']}] not found. No conversion can be done");

                $images_to_process[$key]['unavailable'] = true;
                continue;
            }

            try {
                // PHASE 2.3 — Try AVIF locally first if Imagick + ImageMagick build supports it.
                // AVIF is 20-30% smaller than WebP at equivalent visual quality and is now
                // supported by 94%+ of browsers (caniuse 2026). Falls back to WebP if local
                // AVIF encoding is unavailable, so behavior is strictly additive.
                $pathInfo = pathinfo($image['path']);
                $prefer_avif = self::lwsop_local_supports_avif()
                    && apply_filters('lwsop_prefer_local_avif', true, $image);

                if ($prefer_avif) {
                    $conversion_path = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_lwsoptimized.avif';
                    $this->write_log("Converting image [{$image['path']}] to AVIF format (local Imagick)");
                    $response = $this->convert_image_standard($image['path'], $conversion_path, $quality, $image['format'], 'avif', $max_size);
                    // If local AVIF failed for any reason, fallback to WebP transparently
                    $tmp = json_decode($response, true);
                    if (!is_array($tmp) || (isset($tmp['code']) && $tmp['code'] !== 'OK' && $tmp['code'] !== 'SUCCESS' && !$this->wp_filesystem->exists($conversion_path))) {
                        $this->write_log("Local AVIF conversion failed for [{$image['path']}], falling back to WebP");
                        $conversion_path = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_lwsoptimized.webp';
                        $response = $this->convert_image_standard($image['path'], $conversion_path, $quality, $image['format'], 'webp', $max_size);
                    }
                } else {
                    // No local AVIF — keep the existing WebP path.
                    $conversion_path = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_lwsoptimized.webp';
                    $this->write_log("Converting image [{$image['path']}] to WebP format");
                    $response = $this->convert_image_standard($image['path'], $conversion_path, $quality, $image['format'], 'webp', $max_size);
                }
            } catch (\Exception $e) {
                $this->write_log("Failed to convert image [{$image['path']}]. Error: {$e->getMessage()}");

                $images_to_process[$key]['unavailable'] = true;
                error_log(json_encode(['code' => 'CONVERSION_ERROR', 'message' => 'Error during image conversion: ' . $e->getMessage(), 'data' => $image]));
                continue;
            }

            $result = json_decode($response, true);

            // Check for JSON decoding errors
            if (json_last_error() !== JSON_ERROR_NONE) {
                $this->write_log("Failed to decode JSON after converting [{$image['path']}]. Error: [" . json_last_error_msg() ."]");

                $images_to_process[$key]['unavailable'] = true;
                error_log(json_encode(['code' => 'JSON_ERROR', 'message' => 'Failed to decode JSON response: ' . json_last_error_msg(), 'data' => $response]));
                continue;
            }

            // Check for API errors
            if (!isset($result['code']) || $result['code'] !== 'SUCCESS') {
                $this->write_log("Failed to convert image [{$image['path']}]. Error code: {$result['code']}");
                $this->write_log("Error message: {$result['message']}");
                $this->write_log("Error data: " . json_encode($result['data']));

                $images_to_process[$key]['unavailable'] = true;
                error_log($response);
                continue;
            }

            $converted_path = $result['data']['optimized_path'] ?? '';
            $converted_format = $result['data']['format'] ?? '';

            if (empty($converted_format)) {
                $this->write_log("Image [{$converted_path}] has no format returned. Conversion has failed and will not be attempted again");

                error_log(json_encode(['code' => 'MISSING_FORMAT', 'message' => 'No format returned', 'data' => $result]));
                $images_to_process[$key]['unavailable'] = true;
                continue;
            }

            $size = $image['size'];
            $converted_size = filesize($converted_path) ?? 0;
            $compression = $converted_size > 0 ? round($converted_size / $size, 2) : 0;

            $image['converted'] = true;
            $image['converted_path'] = $converted_path;
            $image['converted_format'] = $converted_format;
            $image['converted_size'] = $converted_size;
            $image['compression'] = $compression;

            // Only update the attachment if the new format is different from the original
            if ($converted_format !== $image['format']) {
                $attachment = array(
                    'ID' => $key,
                    'post_mime_type' => 'image/' . $converted_format,
                );
                wp_update_post($attachment);
            }

            // Delete old attachment metadata
            delete_post_meta($key, '_wp_attachment_metadata');

            // Update the attachment with the new mime type
            wp_update_attachment_metadata($key, wp_generate_attachment_metadata($key, $converted_path));
            update_post_meta($key, '_wp_attached_file', str_replace(ABSPATH, '', $converted_path));

            $this->write_log("Image [{$converted_path}] has been updated in WordPress. Conversion successful");

            // Regenerate the image thumbnails for the optimized image
            if (function_exists('wp_generate_attachment_metadata')) {
                $metadata = wp_generate_attachment_metadata($key, $converted_path);
                wp_update_attachment_metadata($key, $metadata);

                $this->write_log("Image [{$converted_path}]'s thumbnails are being regenerated");
            }

            // Store the changes to the images
            $images_to_process[$key] = $image;

            // Increment the processed images count
            $images_processed++;
        }

        // Store the changes made to the images data
        $conversion_options['images_listing'] = $images_to_process;

        // If no images were processed in this run and there are no images left to convert
        // or if all remaining images are unavailable, stop the cron
        if ($images_processed == 0) {
            $all_unavailable = true;
            $unconverted_images = 0;

            // Check if there are any images that need conversion and are available
            foreach ($images_to_process as $image) {
                if (empty($image['converted']) || $image['converted'] === false) {
                    $unconverted_images++;
                    // If at least one image is not marked as unavailable, we still have work to do
                    if (empty($image['unavailable']) || $image['unavailable'] === false) {
                        $all_unavailable = false;
                        break;
                    }
                }
            }

            // If there are no unconverted images or all remaining ones are unavailable
            if ($unconverted_images == 0 || $all_unavailable) {
                // Unschedule the cron
                $next_conversion = wp_next_scheduled("lws_optimize_image_conversion_cron");
                if ($next_conversion) {
                    wp_unschedule_event($next_conversion, "lws_optimize_image_conversion_cron");

                    $this->write_log('No more images to convert or all remaining images are unavailable. Stopping cron.');

                    // Update the conversion status
                    $conversion_options['conversion_status'] = false;
                    $conversion_options['next_conversion'] = 0;
                }
            }
        }

        // Update the conversion options in the database
        update_option('lws_optimize_image_conversion_options', $conversion_options);

        // Delete lock for next cron
        delete_transient('lws_optimize_conversion_lock');

        $this->write_log("Removing cron lock. Data updated");

        // Return the $conversion_options and the amount of processed images
        return json_encode(array('code' => 'SUCCESS', 'data' => array('options' => $conversion_options, 'processed' => $images_processed), JSON_PRETTY_PRINT));
    }

    /**
     * Take all images stored in the database and revert them to their original state
     *
     * @return string JSON response with the number of images processed, or error message
     */
    public function lws_optimize_image_deconversion_cron()
    {
        // Check if another conversion process is already running
        $conversion_lock = get_transient('lws_optimize_conversion_lock');
        if ($conversion_lock) {
            $this->write_log('Cron already ongoing. Waiting to deconvert');

            // Process is already running, exit
            return;
        }

        // Set a lock that expires in 5 minutes (300 seconds)
        // This ensures only one cron job runs at a time
        set_transient('lws_optimize_conversion_lock', true, 300);

        $this->write_log('Cron lock now in place');

        // Process up to 10 images per cron run to avoid timeouts
        $images_processed = 0;

        // Determine max_images_per_run based on PHP's max_execution_time
        $max_execution_time = ini_get('max_execution_time');
        // If max_execution_time is 0 (unlimited) or high, use a reasonable default
        if ($max_execution_time == 0 || $max_execution_time > 90) {
            $max_images_per_run = 20;
        } else {
            // Estimate approximately 5-6 seconds per image conversion
            // For 30 second timeout, process 5 images; for 60 seconds, process 10
            $max_images_per_run = max(1, min(10, floor($max_execution_time / 3)));
        }

        // Get all format allowed to be converted
        $format = $this->format;

        $conversion_options = get_option('lws_optimize_image_conversion_options', []);

        $images_to_process = $conversion_options['images_listing'] ?? [];
        $converted_images = 0;
        // Check if there are any images that need deconversion and are available
        foreach ($images_to_process as $image) {
            if (!empty($image['converted']) && $image['converted']) {
                $converted_images++;
            }
        }

        $this->write_log('Images to deconvert: ' . $converted_images);

        foreach ($images_to_process as $key => $image) {
            // Check if we have reached the maximum number of images to process
            if ($images_processed >= $max_images_per_run) {
                $this->write_log('Maximum reached. Stopping cron at ' . $max_images_per_run . ' images deconverted');
                break;
            }

            // If there is no converted key, then consider the image as not converted and as such ignore it
            if (empty($image['converted'])) {
                continue;
            }

            // Do not deconvert images that are not converted
            if (!$image['converted'] && strpos($image['converted_path'], '_lwsoptimized') === false) {
                continue;
            }

            $image['path'] = str_replace('_lwsoptimized', '', $image['path']);

            // If the original image cannot be found, then consider this image as unavailable
            if (empty($image['path']) || !file_exists($image['path'])) {
                $this->write_log("Original image at [{$image['path']}] not found. No deconversion can be done");
                $images_to_process[$key]['unavailable'] = true;
                continue;
            }

            $format = $image['format'];
            if (empty($format)) {
                $this->write_log("Original image at [{$image['path']}] has no format. No deconversion can be done");

                error_log(json_encode(['code' => 'MISSING_FORMAT', 'message' => 'No format found for the original image']));
                $images_to_process[$key]['unavailable'] = true;
                continue;
            }
            // Only update the attachment if the format is different
            if ($format !== $image['converted_format']) {
                $attachment = array(
                    'ID' => $key,
                    'post_mime_type' => 'image/' . $format,
                );
                wp_update_post($attachment);
            }

            // Delete old attachment metadata first
            delete_post_meta($key, '_wp_attachment_metadata');

            // Update the attachment with original file and mime type
            wp_update_attachment_metadata($key, wp_generate_attachment_metadata($key, $image['path']));
            update_post_meta($key, '_wp_attached_file', str_replace(ABSPATH, '', $image['path']));

            $this->write_log("Image [{$image['path']}] has been updated in WordPress. Deconversion successful");

            // Regenerate the image thumbnails for the optimized image
            if (function_exists('wp_generate_attachment_metadata')) {
                $metadata = wp_generate_attachment_metadata($key, $image['path']);
                wp_update_attachment_metadata($key, $metadata);

                $this->write_log("Image [{$image['path']}]'s thumbnails are being regenerated");
            }

            if (file_exists($image['converted_path'])) {
                // Delete the converted image
                wp_delete_file($image['converted_path']);

                $this->write_log("Converted image [{$image['converted_path']}] deleted");
            }

            $image['converted'] = false;
            $image['converted_path'] = '';
            $image['converted_format'] = '';
            $image['converted_size'] = 0;
            $image['compression'] = 0;
            $image['unavailable'] = false;

            // Store the changes to the images
            $images_to_process[$key] = $image;

            // Increment the processed images count
            $images_processed++;
        }

        // If no images were processed in this run, stop the cron
        if ($images_processed == 0) {
            // Check if there are any remaining converted images that need to be deconverted
            $remaining_converted = false;
            foreach ($images_to_process as $image) {
                if (!empty($image['converted']) && $image['converted'] === true) {
                    $remaining_converted = true;
                    break;
                }
            }

            // If there are no remaining converted images, stop the cron
            if (!$remaining_converted) {
                // Unschedule the cron
                $next_deconversion = wp_next_scheduled("lws_optimize_image_deconversion_cron");
                if ($next_deconversion) {
                    wp_unschedule_event($next_deconversion, "lws_optimize_image_deconversion_cron");

                    $this->write_log('No more images to deconvert. Stopping cron.');

                    // Update the deconversion status
                    $conversion_options['deconversion_status'] = false;
                    $conversion_options['next_deconversion'] = 0;
                }
            }
        }

        // Store the changes made to the images data
        $conversion_options['images_listing'] = $images_to_process;

        // Update the conversion options in the database
        update_option('lws_optimize_image_conversion_options', $conversion_options);

        // Delete lock for next cron
        delete_transient('lws_optimize_conversion_lock');

        $this->write_log('Removing cron lock. Data updated');

        // Return the $conversion_options and the amount of processed images
        return json_encode(array('code' => 'SUCCESS', 'data' => array('options' => $conversion_options, 'processed' => $images_processed), JSON_PRETTY_PRINT));
    }


    /**
     * On the homepage of most WordPress websites, the other function will not work natively
     * as images are loaded differently, generally by the theme.
     * As such we are forced to use ob_start to get and replace images
     */
    public function lws_optimize_start_output_buffer()
    {
        // Apply to all pages, not just homepage
        if (is_front_page() || is_home()) {
            ob_start([$this, 'lws_optimize_replace_images_api']);
        }
    }

    /**
     * Hijack the uploading process to create a new version of the given $file.
     * This will be called when the user uploads a new image, converting it before it gets added by WordPress.
     * It will not upload the original file at all
     */
    public function lws_optimize_autoupload_images($file)
    {
        // Only convert if the file type is image ; otherwise just return the untouched $file array
        if (substr($file['type'], 0, 5) === "image") {
            // Only convert JPGs, PNGs and WebP
            $format = $this->format;

            $format_string = implode('|', $format);

            // Extract file information
            $file_path = $file['tmp_name'];
            $file_type = $file['type'];

            $tmp = explode("/", $file_type);
            $mime_type = $tmp[0] == "image" ? $tmp[1] : null;

            // If the file has no mime-type or the type is not supported, return the original file
            if (empty($mime_type) || !in_array($mime_type, $format)) {
                error_log(json_encode(['code' => 'INVALID_ORIGIN', 'message' => 'Given file is not an image or mime-type is invalid/not supported.', 'data' => $file]));
                return $file;
            }

            // Depending on which autoconversion type has been chosen, we will use the API or the standard method
            // If nothing is found, default to using the standard method
            $autoconversion_options = get_option('lws_optimize_image_autoconversion_options', []);
            if (isset($autoconversion_options['type']) && $autoconversion_options['type'] == "api") {
                $response = $this->convert_image($file_path, $file_path);
            } else {
                $quality = $autoconversion_options['quality'] ?? 'balanced';
                $max_size = $autoconversion_options['size'] ?? 2560;
                $response = $this->convert_image_standard($file_path, $file_path, $quality, $mime_type, "webp", $max_size);
            }

            $result = json_decode($response, true);

            // Check for JSON decoding errors
            if (json_last_error() !== JSON_ERROR_NONE) {
                error_log(json_encode(['code' => 'JSON_ERROR', 'message' => 'Failed to decode JSON response: ' . json_last_error_msg(), 'data' => $response]));
                return $file;
            }

            // Check for API errors
            if (!isset($result['code']) || $result['code'] !== 'SUCCESS') {
                error_log($response);
                return $file;
            }

            // Get the chosen format (AVIF or WebP), the new PATH and the remaining credits
            $output_format = $result['data']['format'] ?? null;
            $optimized_path = $result['data']['optimized_path'] ?? null;

            // Check if an optimized path was returned
            if (empty($optimized_path) || !file_exists($optimized_path)) {
                error_log(json_encode(['code' => 'MISSING_PATH', 'message' => 'No optimized path returned or file does not exist', 'data' => $result]));
                return $file;
            }

            // Change the parameters of the uploaded file
            $type = strtolower($output_format);
            $file['type'] = "image/$type";
            $file['name'] = preg_replace("/\.($format_string)$/i", ".$type", $file['name']);
            // Set the full path correctly, including directory if it exists
            $file['full_path'] = isset($file['full_path']) ? pathinfo($file['full_path'], PATHINFO_DIRNAME) . '/' . $file['name'] : $file['name'];
            // Update size to match the new file
            $file['size'] = filesize($optimized_path);
        }

        return $file;
    }

    /**
     * Replace images in the content with their optimized versions
     * Enhanced to handle more complex cases and edge cases
     */
    public function lws_optimize_replace_images_api($content)
    {
        // Skip if content is empty or doesn't contain any images
        if (empty($content) || (strpos($content, '<img') === false && strpos($content, 'background') === false)) {
            return $content;
        }

        // Store site URL to avoid repeated calls
        static $site_url = null;
        static $upload_dir = null;
        static $optimized_cache = [];

        if ($site_url === null) {
            $site_url = site_url();
            $uploads = wp_upload_dir();
            $upload_dir = $uploads['basedir'];
        }

        // First pass: Replace <img> tags
        $content = preg_replace_callback(
            '/<img[^>]+src=([\'"])([^\'"]*)\\1[^>]*>/i',
            function ($matches) use ($site_url, $upload_dir, &$optimized_cache) {
                $img_tag = $matches[0];
                $image_url = $matches[2];

                // Skip external images or already optimized images
                if (strpos($image_url, $site_url) === false ||
                    strpos($image_url, '_lwsoptimized') !== false) {
                    return $img_tag;
                }

                // Get server path from URL
                $image_path = $this->url_to_path($image_url);
                if (!$image_path || !file_exists($image_path)) {
                    return $img_tag;
                }

                // Look for optimized versions
                $pathInfo = pathinfo($image_path);
                $filename = $pathInfo['filename'];
                $dirname = $pathInfo['dirname'];

                // Check if this is a thumbnail by looking for dimensions in filename (e.g. -150x150)
                if (preg_match('/-(\d+x\d+)$/', $filename, $matches)) {
                    // Extract base name before dimensions and dimensions separately
                    $baseName = preg_replace('/-(\d+x\d+)$/', '', $filename);
                    $dimensions = $matches[1];

                    // First check AVIF with _lwsoptimized before dimensions
                    $optimized_avif = "$dirname/{$baseName}_lwsoptimized{$dimensions}.avif";
                    if (file_exists($optimized_avif)) {
                        $optimized_url = str_replace($image_path, $optimized_avif, $image_url);
                        $optimized_cache[$image_path] = $optimized_url;

                        // Add type attribute for browsers
                        $img_tag = str_replace($image_url, $optimized_url, $img_tag);
                        if (strpos($img_tag, 'type=') === false) {
                            $img_tag = str_replace('<img ', '<img type="image/avif" ', $img_tag);
                        }
                        return $img_tag;
                    }

                    // Then check WebP with _lwsoptimized before dimensions
                    $optimized_webp = "$dirname/{$baseName}_lwsoptimized{$dimensions}.webp";
                    if (file_exists($optimized_webp)) {
                        $optimized_url = str_replace($image_path, $optimized_webp, $image_url);
                        $optimized_cache[$image_path] = $optimized_url;

                        // Add type attribute for browsers
                        $img_tag = str_replace($image_url, $optimized_url, $img_tag);
                        if (strpos($img_tag, 'type=') === false) {
                            $img_tag = str_replace('<img ', '<img type="image/webp" ', $img_tag);
                        }
                        return $img_tag;
                    }
                } else {
                    // Regular image (not thumbnail)
                    // First check AVIF (higher priority)
                    $optimized_avif = "$dirname/{$filename}_lwsoptimized.avif";
                    if (file_exists($optimized_avif)) {
                        $optimized_url = str_replace($image_path, $optimized_avif, $image_url);
                        $optimized_cache[$image_path] = $optimized_url;

                        // Add type attribute for browsers
                        $img_tag = str_replace($image_url, $optimized_url, $img_tag);
                        if (strpos($img_tag, 'type=') === false) {
                            $img_tag = str_replace('<img ', '<img type="image/avif" ', $img_tag);
                        }
                        return $img_tag;
                    }

                    // Then check WebP
                    $optimized_webp = "$dirname/{$filename}_lwsoptimized.webp";
                    if (file_exists($optimized_webp)) {
                        $optimized_url = str_replace($image_path, $optimized_webp, $image_url);
                        $optimized_cache[$image_path] = $optimized_url;

                        // Add type attribute for browsers
                        $img_tag = str_replace($image_url, $optimized_url, $img_tag);
                        if (strpos($img_tag, 'type=') === false) {
                            $img_tag = str_replace('<img ', '<img type="image/webp" ', $img_tag);
                        }
                        return $img_tag;
                    }
                }

                // No optimized version found
                $optimized_cache[$image_path] = false;
                return $img_tag;
            },
            $content
        );

        // Second pass: Replace CSS background images
        $content = preg_replace_callback(
            '/url\(([\'"]?)([^\'"\)]+)\\1\)/i',
            function ($matches) use ($site_url, $upload_dir, &$optimized_cache) {
                $full_match = $matches[0];
                $image_url = $matches[2];

                // Skip external images, SVGs, or already optimized images
                if (strpos($image_url, $site_url) === false ||
                    strpos($image_url, '.svg') !== false ||
                    strpos($image_url, '_lwsoptimized') !== false) {
                    return $full_match;
                }

                // Get server path from URL
                $image_path = $this->url_to_path($image_url);
                if (!$image_path || !file_exists($image_path)) {
                    return $full_match;
                }

                // Check cache first
                if (isset($optimized_cache[$image_path])) {
                    if ($optimized_cache[$image_path]) {
                        return str_replace($image_url, $optimized_cache[$image_path], $full_match);
                    }
                    return $full_match;
                }

                // Look for optimized versions
                $pathInfo = pathinfo($image_path);
                $filename = $pathInfo['filename'];
                $dirname = $pathInfo['dirname'];

                // First check AVIF (higher priority)
                $optimized_avif = "$dirname/{$filename}_lwsoptimized.avif";
                if (file_exists($optimized_avif)) {
                    $optimized_url = str_replace($image_path, $optimized_avif, $image_url);
                    $optimized_cache[$image_path] = $optimized_url;
                    return str_replace($image_url, $optimized_url, $full_match);
                }

                // Then check WebP
                $optimized_webp = "$dirname/{$filename}_lwsoptimized.webp";
                if (file_exists($optimized_webp)) {
                    $optimized_url = str_replace($image_path, $optimized_webp, $image_url);
                    $optimized_cache[$image_path] = $optimized_url;
                    return str_replace($image_url, $optimized_url, $full_match);
                }

                // No optimized version found
                $optimized_cache[$image_path] = false;
                return $full_match;
            },
            $content
        );

        return $content;
    }

    /**
     * Helper function to convert image URLs to server paths
     */
    private function url_to_path($url) {
        // Remove query strings
        $url = preg_replace('/\?.*/', '', $url);

        // Convert URL to server path
        $site_url = site_url();
        $home_url = home_url();
        $upload_url = wp_upload_dir()['baseurl'];
        $upload_dir = wp_upload_dir()['basedir'];

        // Try multiple methods to find the file path
        if (strpos($url, $upload_url) !== false) {
            // Standard uploads folder
            return str_replace($upload_url, $upload_dir, $url);
        } elseif (strpos($url, $home_url) !== false) {
            // Main home URL
            return str_replace($home_url, ABSPATH, $url);
        } elseif (strpos($url, $site_url) !== false) {
            // Site URL (might be different from home)
            return str_replace($site_url, ABSPATH, $url);
        } else {
            // Relative URL
            if (strpos($url, '/') === 0) {
                return ABSPATH . ltrim($url, '/');
            }
        }

        return false;
    }

    /**
     * Convert the given image to WebP or AVIF (taking the best of the 2).
     * This uses an external API to convert the image
     *
     * @param string $path The path to the image
     * @param string|null $endpath The PATH where to save the new image (mainly to use with the on-upload conversion)
     * @return string JSON response from the API, either an error or the converted image
     */
    public function convert_image($path, $endpath = null, $origin = null) {
        // The API Key is unique to each domain and generated by the API the first time.
        // If using a key not corresponding with the domain, then the API will return an error.
        $api_key = get_option('lws_optimize_image_api_key', false);

        // Check for the existence of the file at $path
        if (!$this->wp_filesystem->exists($path)) {
            return json_encode(['code' => 'FILE_NOT_FOUND', 'message' => 'File not found', 'data' => $path]);
        }

        if (empty($origin)) {
            $wpdb = $GLOBALS['wpdb'];
            $origin = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = 'siteurl'");
        }

        // Create the file to be sent via cURL
        $mime = mime_content_type($path);
        $cfile = new \CURLFile($path, $mime, basename($path));

        // Requête cURL
        $ch = curl_init('https://compress.lwspanel.com/compress-image');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            'image' => $cfile,
        ]);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Origin: ' . rtrim($origin, '/'),
            $api_key ? "X-Api-Key: $api_key" : null
        ]);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 120);

        $response = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($code == 402) {
            $result = json_decode($response, true);
            $api_key = $result['apiKey'] ?? null;
            update_option('lws_optimize_image_api_key', $api_key);


            return json_encode(['code' => 'NO_CREDITS', 'message' => 'No credits left for this domain', 'data' => $response]);
        }

        if ($code !== 200) {
            return json_encode(['code' => 'HTTP_ERROR', 'message' => "$code", 'data' => $response]);
        }

        $result = json_decode($response, true);

        // Check for JSON decoding errors
        if (json_last_error() !== JSON_ERROR_NONE) {
            return json_encode(['code' => 'JSON_ERROR', 'message' => 'Failed to decode JSON response: ' . json_last_error_msg(), 'data' => $response]);
        }

        $image = $result['base64'] ?? null;
        $format = $result['format'] ?? null;
        $remaining_credits = $result['remainingCredits'] ?? null;
        $api_key = $result['apiKey'] ?? null;

        update_option('lws_optimize_image_api_key', $api_key);

        if (empty($image) || empty($format)) {
            return json_encode(['code' => 'FAIL_OPTIMIZE', 'message' => 'The given image could not be optimized', 'data' => $response]);
        }

        // Save the image in the same directory as the original, replacing the original extension with the new one
        // If the $endpath is provided, save the image there instead
        $pathInfo = pathinfo($path);
        $outputPath = !empty($endpath) ? $endpath : $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_lwsoptimized.' . $format;

        // Save the optimized image
        if (file_put_contents($outputPath, base64_decode($image)) === false) {
            return json_encode(['code' => 'SAVE_ERROR', 'message' => 'Failed to save optimized image', 'data' => $outputPath]);
        }

        return json_encode(['code' => 'SUCCESS', 'message' => 'Image successfully optimized','data' => [
                'image_path' => $path,
                'optimized_path' => $outputPath,
                'format' => $format,
                'remaining_credits' => $remaining_credits
            ]
        ]);
    }

    /**
     * Create a copy of the given $image, convert it to the $end type from the current $origin.
     * The image will then be saved to $output. $output and $image can be the same to replace the image.
     * This is free and does not use any API or credits.
     *
     * @param string $image The PATH to the image to convert
     * @param string $origin The mime-type in which the image currently is. Format : image/png
     * @param string $end The mime-type in which the image needs to be converted. Format : image/webp
     * @param string $output The PATH where to save the newly converted image
     *
     * @return bool Either true on success or false on error
     */
    public function convert_image_standard(string $image, string $output, string $quality = 'balanced', string $origin = "jpeg", string $end = "webp", $max_size = 2560)
    {
        $timer = microtime(true);

        $this->write_log("Starting process to convert image [{$image}] with Imagick");

        try {
            // Validate parameters
            if (empty($image) || empty($output) || empty($origin) || empty($end)) {
                $this->write_log("Missing parameters [" . $image ?? 'NO IMAGE' . "] [" . $output ?? 'NO OUTPUT' . "] [" . $origin ?? 'NO ORIGIN' . "][" . $end ?? 'NO END' . "]");
                return json_encode(['code' => 'NO_PARAMETERS', 'message' => 'Missing required parameters', 'time' => microtime(true) - $timer]);
            }

            // Check for Imagick availability
            if (!extension_loaded('imagick') || !class_exists('Imagick')) {
                $this->write_log("Imagick not found. Aborting");
                return json_encode(['code' => 'IMAGICK_NOT_FOUND', 'message' => 'Imagick extension required for image optimization', 'time' => microtime(true) - $timer]);
            }

            // Parse image types
            $starting_type = preg_replace('/^image\//', '', strtolower($origin));
            $ending_type = preg_replace('/^image\//', '', strtolower($end));

            // Create Imagick instance
            $img = new \Imagick();
            $supported_formats = $img->queryFormats();

            // Check format support
            if (!in_array(strtoupper($starting_type), $supported_formats) || !in_array(strtoupper($ending_type), $supported_formats)) {
                $this->write_log("Image [{$image}] cannot be converted as either {$starting_type} or {$ending_type} is not supported");

                return json_encode([
                    'code' => 'UNSUPPORTED_FORMAT',
                    'message' => 'Image format not supported by Imagick',
                    'data' => ['source' => $starting_type, 'target' => $ending_type],
                    'time' => microtime(true) - $timer
                ]);
            }

            // Read source image
            if (!$this->wp_filesystem->exists($image) || !$img->readImage($image)) {
                $this->write_log("Failed to read [{$image}], cannot convert");
                return json_encode(['code' => 'IMAGE_UNREADABLE', 'message' => 'Source image not readable', 'data' => $image, 'time' => microtime(true) - $timer]);
            }

            // Resize if needed
            $width = $img->getImageWidth();
            $height = $img->getImageHeight();

            if ($width > $max_size) {
                $newHeight = intval(($max_size / $width) * $height);
                $img->resizeImage($max_size, $newHeight, \Imagick::FILTER_LANCZOS, 1);
            }

            // Strip metadata only if explicitly requested via filter (default: keep EXIF/XMP)
            if (apply_filters('lwsop_strip_image_metadata', false, $image)) {
                $img->stripImage();
            }

            // Set compression quality (2026 defaults: balanced = 80, was 64)
            switch ($quality) {
                case 'low':
                    $quality = 60;
                    break;
                case 'balanced':
                    $quality = 80;
                    break;
                case 'high':
                    $quality = 92;
                    break;
                default:
                    $quality = 80;
            }

            // Auto-detect lossless for PNG/GIF logos/icons/small images
            $lossless = \Lws\Classes\Images\LwsOptimizeImageOptimization::lwsop_should_use_lossless(
                'image/' . $starting_type, $width, $height, 0
            );
            $lower = strtolower($ending_type);
            if ($lossless && ($lower === 'webp' || $lower === 'avif')) {
                $img->setOption($lower . ':lossless', 'true');
                if ($lower === 'webp') {
                    $img->setOption('webp:method', '6');
                }
            } else {
                $img->setImageCompressionQuality($quality);
                if ($lower === 'webp') {
                    $img->setOption('webp:method', '6'); // Better compression
                }
            }

            // Handle format conversion
            if (!$img->setImageFormat($ending_type)) {
                $this->write_log("Failed to convert [{$image}] to {$ending_type}");
                return json_encode(['code' => 'CONVERSION_FAIL', 'message' => 'Format conversion failed', 'time' => microtime(true) - $timer]);
            }

            // Write output image
            if (!$img->writeImage($output)) {
                // Fallback to alternative write method
                $fp = fopen($output, "wb");
                if (!$fp || !$img->writeImageFile($fp)) {
                    if ($fp) {
                        fclose($fp);
                    }

                    $this->write_log("Failed to write image [{$image}], conversion failed");

                    return json_encode(['code' => 'WRITE_FAIL', 'message' => 'Failed to write output image', 'time' => microtime(true) - $timer]);
                }
                fclose($fp);
            }

            // Clean up resources
            $img->clear();
            $img->destroy();

            $this->write_log("Image [{$image}] converted from {$starting_type} to {$ending_type}");

            return json_encode(['code' => 'SUCCESS', 'message' => 'Image successfully optimized','data' => [
                'image_path' => $image,
                'optimized_path' => $output,
                'format' => $end,
            ]]);
        } catch (\Exception $e) {
            return json_encode([
                'code' => 'ERROR',
                'message' => $e->getMessage(),
                'line' => $e->getLine(),
                'time' => microtime(true) - $timer
            ]);
        }
    }

    /**
     * Start or stop the autoconversion process using the API
     */
    public function lws_optimize_start_autoconversion_api() {
        check_ajax_referer('nonce_for_lws_optimize_start_autoconversion_api', '_ajax_nonce');

        $this->write_log('Activating API autoconversion on upload');

        $state = boolval($_POST['state'] ?? false);

        // Get the autoconversion options from the DB (if any)
        $autoconversion_options = get_option('lws_optimize_image_autoconversion_options', []);

        $autoconversion_options = [
            'state' => $state,
            'type' => 'api',
        ];

        // Update the autoconversion options in the database
        update_option('lws_optimize_image_autoconversion_options', $autoconversion_options);

        if ($state) {
            $this->write_log('Autoconversion has been activated using API');
        } else {
            $this->write_log('Autoconversion has been deactivated');
        }

        wp_die(json_encode(array('code' => 'SUCCESS', 'data' => $autoconversion_options, JSON_PRETTY_PRINT)));
    }

    /**
     * Start or stop the autoconversion process using Imagick
     */
    public function lws_optimize_start_autoconversion_standard() {
        check_ajax_referer('nonce_for_lws_optimize_start_autoconversion_standard', '_ajax_nonce');

        $this->write_log('Activating standard autoconversion on upload');

        $quality = sanitize_text_field($_POST['quality'] ?? 'balanced');
        $size = intval($_POST['size'] ?? 2560);
        $state = sanitize_text_field($_POST['state'] ?? "false");

        // Get the autoconversion options from the DB (if any)
        $autoconversion_options = get_option('lws_optimize_image_autoconversion_options', []);

        $autoconversion_options = [
            'quality' => $quality,
            'size' => $size,
            'state' => $state == "false" ? false : true,
            'type' => 'standard',
        ];

        // Update the autoconversion options in the database
        update_option('lws_optimize_image_autoconversion_options', $autoconversion_options);

        if ($state) {
            $this->write_log('Autoconversion has been activated using Imagick');
        } else {
            $this->write_log('Autoconversion has been deactivated');
        }

        wp_die(json_encode(array('code' => 'SUCCESS', 'data' => $autoconversion_options, JSON_PRETTY_PRINT)));
    }

    /**
     * Get the remaining credits for the current domain
     * @return string JSON response with the remaining credits or an error message. Also contains the APi Key and the domains
     */
    public function get_remaining_credits() {
        // Get the API Key saved in database ; If there is none, we cannot check credits
        $api_key = get_option('lws_optimize_image_api_key', false);

        $ch = curl_init('https://compress.lwspanel.com/credits');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Origin: ' . site_url() ?: '',
            $api_key ? "X-Api-Key: $api_key" : null
        ]);

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $response = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($code !== 200) {
            return json_encode(['code' => 'HTTP_ERROR', 'message' => "$code", 'data' => $response]);
        }

        $result = json_decode($response, true);

        // Check for JSON decoding errors
        if (json_last_error() !== JSON_ERROR_NONE) {
            return json_encode(['code' => 'JSON_ERROR', 'message' => 'Failed to decode JSON response: ' . json_last_error_msg(), 'data' => $response]);
        }

        $credits = $result['credits'] ?? 2000;
        $domains = $result['domains'] ?? null;
        $api_key = $result['apiKey'] ?? null;

        update_option('lws_optimize_image_api_key', $api_key);

        if ($credits === null) {
            return json_encode(['code' => 'FAIL_GET_CREDITS', 'message' => 'Failed to retrieve credits for the domain', 'data' => $response]);
        }

        return json_encode(['code' => 'SUCCESS', 'message' => 'Credits retrieved successfully', 'data' => [
                'api_key' => $api_key,
                'returned_api_key' => $api_key,
                'credits' => $credits,
                'domains' => $domains
            ]
        ]);
    }

    public function lwsOpSizeConvert($size)
    {
        $unit = array(__('b', 'lws-optimize'), __('K', 'lws-optimize'), __('M', 'lws-optimize'), __('G', 'lws-optimize'), __('T', 'lws-optimize'), __('P', 'lws-optimize'));
        if ($size <= 0) {
            return '0 ' . $unit[1];
        }
        return @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . '' . $unit[$i];
    }
}