<?php

/**
 * Class - Ajax
 */
class wps_ic_ajax extends wps_ic
{

    public static $API_URL = WPS_IC_CRITICAL_API_URL;
    public static $PAGESPEED_URL = WPS_IC_PAGESPEED_API_URL;
    public static $PAGESPEED_URL_HOME = WPS_IC_PAGESPEED_API_URL_HOME;
    public static $CRITICAL_URL_HOME = WPS_IC_CRITICAL_API_URL_HOME;


    public static $local;
    public static $options;
    public static $settings;
    public static $accountStatus;

    public static $logo_compressed;
    public static $logo_uncompressed;
    public static $logo_excluded;
    public static $count_thumbs;

    public static $cacheIntegrations;

    public static $version;
    public static $Requests;
    public static $apikey;

    public function __construct()
    {
        self::$Requests = new wps_ic_requests();

        if (is_admin()) {
            self::$version = str_replace('.', '', parent::$version);
            self::$cacheIntegrations = new wps_ic_cache_integrations();
            self::$settings = get_option(WPS_IC_SETTINGS);
            self::$options = get_option(WPS_IC_OPTIONS);

            // Sync live-cdn from actual state: CF CDN or any CDN file type on
            if (empty(self::$settings['live-cdn']) || self::$settings['live-cdn'] != '1') {
                $cfSettings = get_option(WPS_IC_CF);
                if (!empty($cfSettings['settings']['cdn']) && $cfSettings['settings']['cdn'] == '1') {
                    self::$settings['live-cdn'] = '1';
                } else {
                    $cdnOn = false;
                    if (!empty(self::$settings['serve'])) {
                        foreach (self::$settings['serve'] as $v) {
                            if ($v == '1') { $cdnOn = true; break; }
                        }
                    }
                    if (!$cdnOn && (!empty(self::$settings['css']) && self::$settings['css'] == '1')) $cdnOn = true;
                    if (!$cdnOn && (!empty(self::$settings['js']) && self::$settings['js'] == '1')) $cdnOn = true;
                    if (!$cdnOn && (!empty(self::$settings['fonts']) && self::$settings['fonts'] == '1')) $cdnOn = true;
                    if ($cdnOn) self::$settings['live-cdn'] = '1';
                }
            }

            self::$apikey = parent::$api_key;
            self::$count_thumbs = count(get_intermediate_image_sizes());
            self::$local = parent::$local;
            self::$logo_compressed = WPS_IC_URI . 'assets/images/legacy/logo-compressed.svg';
            self::$logo_uncompressed = WPS_IC_URI . 'assets/images/legacy/logo-not-compressed.svg';
            self::$logo_excluded = WPS_IC_URI . 'assets/images/legacy/logo-excluded.svg';

            // GUI switch — must be available even without API key (lite CF banner uses it)
            $this->add_ajax('wpsChangeGui');

            if (!empty(parent::$api_key)) {
                // Pull Stats
                $this->add_ajax('wps_fetchInitialTest');
                $this->add_ajax('wps_ic_pull_stats');

                // Scan Fonts
                $this->add_ajax('wpsRemoveFont');
                $this->add_ajax('wpsScanFonts');

                // Cloudflare
                $this->add_ajax('wpc_ic_checkCFToken');
                $this->add_ajax('wpc_ic_checkCFConnect');
                $this->add_ajax('wpc_ic_checkCFDisconnect');
                $this->add_ajax('wpc_ic_refreshCFConnection');
                $this->add_ajax('wpc_ic_setupCF');

                // Critical CSS
                $this->add_ajax('wps_ic_critical_get_assets');
                $this->add_ajax('wps_ic_critical_run');
                $this->add_ajax('wps_ic_get_setting');
                $this->add_ajax('wps_ic_saveSetting');
                $this->add_ajax('wps_ic_save_excludes_settings');

                // GeoLocation for Popups
                $this->add_ajax('wps_ic_remove_key');
                $this->add_ajax('wpc_ic_set_mode');
                $this->add_ajax('wpc_ic_ajax_set_preset');
                $this->add_ajax('wps_ic_cname_add');
                $this->add_ajax('wps_ic_cname_retry');
                $this->add_ajax('wps_ic_remove_cname');
                $this->add_ajax('wps_ic_exclude_list');
                $this->add_ajax('wps_ic_geolocation');
                $this->add_ajax('wps_ic_geolocation_force');

                // Bulk Actions
                $this->add_ajax('wps_ic_StopBulk');
                $this->add_ajax('wps_ic_getBulkStats');
                $this->add_ajax('wps_ic_bulkCompressHeartbeat');
                $this->add_ajax('wps_ic_bulkRestoreHeartbeat');
                $this->add_ajax('wps_ic_isBulkRunning');
                $this->add_ajax('wpc_ic_start_bulk_restore');
                $this->add_ajax('wpc_ic_start_bulk_compress');
                $this->add_ajax('wps_ic_media_library_bulk_heartbeat');
                $this->add_ajax('wps_ic_doBulkRestore');
                $this->add_ajax('wps_ic_RestoreFinished');

                $this->add_ajax('wps_ic_media_library_heartbeat');
                $this->add_ajax('wps_ic_compress_live');
                $this->add_ajax('wps_ic_restore_live');
                $this->add_ajax('wps_ic_exclude_live');
                $this->add_ajax('wps_ic_get_default_settings');

                $this->add_ajax('wps_ic_ajax_v2_checkbox');
                $this->add_ajax('wps_ic_ajax_v2_checkbox_batch');
                $this->add_ajax('wps_ic_purge_after_save');
                $this->add_ajax('wps_ic_ajax_checkbox');

                $this->add_ajax('wps_ic_purge_cdn');
                $this->add_ajax('wps_ic_purge_html');
                $this->add_ajax('wps_ic_purge_critical_css');
                $this->add_ajax('wps_ic_preload_page');
                $this->add_ajax('wps_ic_generate_critical_css');

                $this->add_ajax('wps_ic_dismiss_notice');
                $this->add_ajax('wps_ic_fix_notice');
                $this->add_ajax('wps_ic_save_mode');
                $this->add_ajax('wps_ic_get_optimization_status_pages');
                $this->add_ajax('wps_ic_save_optimization_status');

                $this->add_ajax('wps_ic_get_page_excludes_popup_html');
                $this->add_ajax('wps_ic_save_page_excludes_popup');
                $this->add_ajax('wps_ic_resetTest');
                $this->add_ajax('wps_ic_run_tests');
                $this->add_ajax('wps_ic_start_optimizations');
                $this->add_ajax('wps_ic_stop_optimizations');
                $this->add_ajax('wpsRunQuickTest');
                $this->add_ajax('wps_ic_run_single_optimization');
                $this->add_ajax('wps_ic_test_api_connectivity');
                $this->add_ajax('wps_ic_get_per_page_settings_html');
                $this->add_ajax('wps_ic_save_per_page_settings');
                $this->add_ajax('wps_ic_save_purge_hooks_settings');
                $this->add_ajax('wps_ic_save_cache_cookies_settings');
                $this->add_ajax('wps_ic_get_cache_cookies');
                $this->add_ajax('wps_ic_get_purge_rules');
                $this->add_ajax('wps_ic_export_settings');
                $this->add_ajax('wps_ic_import_settings');
                $this->add_ajax('wps_ic_set_default_settings');
                $this->add_ajax('wps_ic_save_cf_cdn');
                $this->add_ajax('wps_ic_get_cf_cdn');

                // Live Start

                // First Run Variable
                $this->add_ajax('wps_ic_count_uncompressed_images');

                // Change Setting
                $this->add_ajax('wps_ic_settings_change');

                // Exclude Image from Compress
                $this->add_ajax('wps_ic_simple_exclude_image');
                $this->add_ajax('wps_lite_connect');
                $this->add_ajax('wps_ic_live_connect');
            } else {
                // Connect
                $this->add_ajax('wps_lite_connect');
                $this->add_ajax('wps_ic_live_connect');
            }

            $this->add_ajax('wps_ic_check_optimization_status');
            $this->add_ajax('wpc_send_critical_remote');
            $this->add_ajax_nopriv('wpc_send_critical_remote');
        } else {
            $this->add_ajax('wpc_ic_set_mode');
            $this->add_ajax('wpc_send_critical_remote');
            $this->add_ajax_nopriv('wpc_send_critical_remote');
            $this->add_ajax('wps_ic_purge_html');
            $this->add_ajax('wps_ic_purge_cdn');
            $this->add_ajax('wps_ic_purge_critical_css');
            $this->add_ajax('wps_ic_preload_page');
            $this->add_ajax('wps_ic_generate_critical_css');
        }
    }

    public function add_ajax($hook)
    {
        add_action('wp_ajax_' . $hook, [$this, $hook]);
    }

    public function add_ajax_nopriv($hook)
    {
        add_action('wp_ajax_nopriv_' . $hook, [$this, $hook]);
    }

    public static function wpc_ic_checkCFDisconnect()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $requests = new wps_ic_requests();

        $cfSettings = get_option(WPS_IC_CF);
        $zoneInput = $cfSettings['zone'];
        $token = $cfSettings['token'];

        $options = get_option(WPS_IC_OPTIONS);
        $apikey = $options['api_key'];

        $siteUrl = site_url();
        $zoneName = str_replace(array('http://', 'https://', '/'), '', $siteUrl);

        // Remove CDN bypass rule before disconnecting
        $cfsdk = new WPC_CloudflareAPI($token);
        $cfsdk->removeCdnBypassRule($zoneInput);

        $c = $requests->GET(WPS_IC_KEYSURL, ['action' => 'disconnectCF', 'token' => $token, 'zone' => $zoneInput, 'zoneName' => $zoneName, 'siteUrl' => $siteUrl, 'apikey' => $apikey, 'time' => microtime(true)], ['timeout' => 120]);

        delete_option(WPS_IC_CF);
        delete_transient('wpc_cdn_backup');
        wp_send_json_success();
    }

    public static function wpc_ic_checkCFConnect()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $token = sanitize_text_field($_POST['token']);
        $zoneInput = sanitize_text_field($_POST['zone']);

        $cfapi = new WPC_CloudflareAPI($token);
        $check = $cfapi->checkPrivileges($zoneInput);
        if (is_wp_error($check)) {
            wp_send_json_error(['msg' => $check->get_error_message()]);
        }
        $zones = $cfapi->listZones();

        if (is_wp_error($zones)) {
            wp_send_json_error($zones->get_error_message());
        } else {
            $zonesOutput = [];
            foreach ($zones['result'] as $zone) {
                #echo "Zone: {$zone['name']}, ID: {$zone['id']}" . PHP_EOL;
                $zonesOutput[$zone['id']] = $zone['name'];
            }

            for ($i = 2; $i <= 20; $i++) {
                $zones = $cfapi->listZones($i);
                if (!empty($zones['result'])) {
                    foreach ($zones['result'] as $zone) {
                        $zonesOutput[$zone['id']] = $zone['name'];
                    }
                } else {
                    break;
                }
            }
        }

        if (!empty($zonesOutput) && !empty($zonesOutput[$zoneInput])) {
            $save = ['token' => $token, 'zone' => $zoneInput, 'zoneName' => $zonesOutput[$zoneInput]];
            update_option(WPS_IC_CF, $save);
            wp_send_json_success($save);
        }

        wp_send_json_error(print_r($zones, true));
    }

    public static function wpc_ic_checkCFToken()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $token = sanitize_text_field($_POST['token']);
        $cfapi = new WPC_CloudflareAPI($token);
        $zones = $cfapi->listZones();

        if (is_wp_error($zones)) {

            $error = 'Unkown error.';
            if ($zones->get_error_message() == 'Invalid request headers') {
                $error = 'Invalid request headers - Invalid API Token.';
            } else if ($zones->get_error_message() == 'Invalid access token') {
                $error = 'Invalid access token - Token format is correct, but the API Token is invalid.';
            } else {
                $error = $zones->get_error_message();
            }

            wp_send_json_error($error);
        } else {
            $zonesOutput = [];

            foreach ($zones['result'] as $zone) {
                $zonesOutput[$zone['id']] = $zone['name'];
            }

            if (!empty($zonesOutput)) {
                foreach ($zonesOutput as $zoneID => $zoneName) {
                    $zonesDropdown .= '<div data-selected-zone="' . $zoneName . '" data-selected-zone-id="' . $zoneID . '">' . $zoneName . '</div>';
                }

                for ($i = 2; $i <= 20; $i++) {
                    $zones = $cfapi->listZones($i);
                    if (!empty($zones['result'])) {
                        foreach ($zones['result'] as $zone) {
                            $zonesDropdown .= '<div data-selected-zone="' . $zone['name'] . '" data-selected-zone-id="' . $zone['id'] . '">' . $zone['name'] . '</div>';
                        }
                    } else {
                        break;
                    }
                }

                wp_send_json_success($zonesDropdown);
            }
        }

        if (empty($zones['result'])) {
            wp_send_json_error('We were unable to connect with Cloudflare API, seems your token is missing required privileges.');
        } else {
            wp_send_json_error('unknown-error');
        }
    }

    public static function isFeatureEnabled($featureName)
    {
        $feature = get_transient($featureName . 'Enabled');
        if (!$feature || $feature == '0') {
            return false;
        }

        return true;
    }


    public function wpc_ic_refreshCFConnection()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $requests = new wps_ic_requests();

        $options = get_option(WPS_IC_OPTIONS);
        $cf = get_option(WPS_IC_CF);

        $apikey = $options['api_key'];

        $token = sanitize_text_field($cf['token']);
        $zoneInput = sanitize_text_field($cf['zone']);

        $siteUrl = site_url();
        $zoneName = str_replace(array('http://', 'https://', '/'), '', $siteUrl);

        // $cf['settings'] = ['assets' => '1', 'edge-cache' => 'all', 'cdn' => '1'];

        $body = $requests->GET(WPS_IC_KEYSURL, ['action' => 'refreshCF', 'token' => $token, 'zone' => $zoneInput, 'siteUrl' => site_url(), 'zoneName' => $zoneName, 'staticAssets' => $cf['settings']['assets'], 'htmlCache' => $cf['settings']['edge-cache'], 'cdn' => $cf['settings']['cdn'], 'apikey' => $apikey, 'time' => microtime(true)], ['timeout' => 120]);

        if (!empty($body)) {
            $data = (array)$body->data;

            $cfCname = $data['cfName'];
            update_option(WPS_IC_CF_CNAME, $cfCname);

            self::$options = get_option(WPS_IC_SETTINGS);
            self::$options['cf'] = $cf['settings'];
            //self::$options['ic_custom_cname'] = $data['cfName'];
            update_option(WPS_IC_SETTINGS, self::$options);

            // Ensure CDN bypass rule + IP whitelist exists
            $cfsdk = new WPC_CloudflareAPI($token);
            $cfsdk->addCdnBypassRule($zoneInput);
            $cfsdk->whitelistIPs($zoneInput);

            wp_send_json_success('cf-refreshed-successfully');
        }

        wp_send_json_error();
    }


    public function wpc_ic_setupCF()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $requests = new wps_ic_requests();

        $token = sanitize_text_field($_POST['token']);
        $zoneInput = sanitize_text_field($_POST['zone']);

        $options = get_option(WPS_IC_OPTIONS);
        $apikey = $options['api_key'];

        // TODO: Add functions
        $cf = get_option(WPS_IC_CF);

        $siteUrl = site_url();
        $zoneName = str_replace(array('http://', 'https://', '/'), '', $siteUrl);

        $body = $requests->GET(WPS_IC_KEYSURL, ['action' => 'setupCF', 'token' => $token, 'zone' => $zoneInput, 'siteUrl' => site_url(), 'zoneName' => $cf['zoneName'], 'staticAssets' => '1', 'htmlCache' => 'all', 'cdn' => '1', 'apikey' => $apikey, 'time' => microtime(true)], ['timeout' => 120]);

        if (!empty($body)) {
            $data = (array)$body->data;

            $cf['custom_cname'] = $data['cfName'];
            $cfCname = $data['cfName'];
            $cf['settings'] = ['assets' => '1', 'edge-cache' => 'all', 'cdn' => '1'];
            update_option(WPS_IC_CF, $cf);
            update_option(WPS_IC_CF_CNAME, $cfCname);

            // Create CDN bypass rule + IP whitelist for country-block protection
            $cfsdk = new WPC_CloudflareAPI($token);
            $cfsdk->addCdnBypassRule($zoneInput);
            $cfsdk->whitelistIPs($zoneInput);

            wp_send_json_success('cf-connected-successfully');
        }

        wp_send_json_error('error');
    }


    public function wpc_send_critical_remote() {
        $criticalCSS = new wps_criticalCss();

        $realUrl = urldecode($_POST['realUrl']);
        $realUrl = sanitize_text_field($realUrl);
        $postID = sanitize_text_field($_POST['postID']);

        /**
         * Check does http/s exist if not add it
         */
        if (strpos($realUrl, 'https://') === false && strpos($realUrl, 'http://') === false) {
            $realUrl = 'https://' . $realUrl;
        }

        /**
         * Only keep allowed params in url
         */
        $keys = new wps_ic_url_key();

        $allowed_params = $keys->get_allowed_params();
        $parsed_url = parse_url($realUrl);
        parse_str($parsed_url['query'], $query_params);

        // Keep only the allowed parameters
        $filtered_params = array_intersect_key($query_params, array_flip($allowed_params));

        // Check if there are any disallowed parameters
        $disallowed_params = array_diff_key($query_params, array_flip($allowed_params));

        if (!empty($disallowed_params)) {
            wp_send_json_success('skipped');
        }

        // Build the new query string
        $new_query = http_build_query($filtered_params);

        // Reconstruct the URL
        $realUrl = $parsed_url['host'] . (isset($parsed_url['path']) ? $parsed_url['path'] : '') . '?' . $new_query;
        $realUrl = rtrim($realUrl, '?');
        $realUrl = rtrim($realUrl, '/');

        /**
         * Does Critical Already Exist?
         */
        $criticalCSSExists = $criticalCSS->criticalExistsAjax($realUrl);
        if (!empty($criticalCSSExists)) {
            wp_send_json_success(['exists', $realUrl, $criticalCSSExists]);
        }

        /**
         * Is Critical Ajax Already Running?
         */
        $ccss_debug = get_option('ccss_debug');
        if (empty($ccss_debug) || $ccss_debug == 'false') {
            $running = get_transient('wpc_critical_ajax_' . $postID);
            if (!empty($running) && $running == 'true') {
                wp_send_json_success(['already-running', $realUrl]);
            }
        }

        // is home
        $home = false;
        $home_url = rtrim(home_url(), '/');
        $realUrl_stripped = preg_replace('#^https?://#', '', $realUrl);
        $home_url_stripped = preg_replace('#^https?://#', '', $home_url);

        if ($home_url_stripped == $realUrl_stripped) {
            $home = true;
        }

        // Set as Running
        set_transient('wpc_critical_ajax_' . $postID, 'true', 60);

        $criticalCSS->sendCriticalUrl($realUrl, 0);

        wp_send_json_success(array('sent', $realUrl));
    }


    public function wpc_send_critical_remote_old()
    {
        $criticalCSS = new wps_criticalCss();

        $realUrl = urldecode($_POST['realUrl']);
        $realUrl = sanitize_text_field($realUrl);
        $postID = sanitize_text_field($_POST['postID']);

        /**
         * Only keep allowed params in url
         */
        $keys = new wps_ic_url_key();

        $allowed_params = $keys->get_allowed_params();
        $parsed_url = parse_url($realUrl);
        parse_str($parsed_url['query'], $query_params);

        // Keep only the allowed parameters
        $filtered_params = array_intersect_key($query_params, array_flip($allowed_params));

        // Check if there are any disallowed parameters
        $disallowed_params = array_diff_key($query_params, array_flip($allowed_params));

        if (!empty($disallowed_params)) {
            wp_send_json_success('skipped');
        }

        // Build the new query string
        $new_query = http_build_query($filtered_params);

        // Reconstruct the URL
        $realUrl = $parsed_url['host'] . (isset($parsed_url['path']) ? $parsed_url['path'] : '') . '?' . $new_query;
        $realUrl = rtrim($realUrl, '?');
        $realUrl = rtrim($realUrl, '/');

        /**
         * Does Critical Already Exist?
         */
        $criticalCSSExists = $criticalCSS->criticalExistsAjax($realUrl);
        if (!empty($criticalCSSExists)) {
            wp_send_json_success(['exists', $realUrl, $criticalCSSExists]);
        }


        /**
         * Is Critical Ajax Already Running?
         */
        $ccss_debug = get_option('ccss_debug');
        if (empty($ccss_debug) || $ccss_debug == 'false') {
            $running = get_transient('wpc_critical_ajax_' . $postID);
            if (!empty($running) && $running == 'true') {
                wp_send_json_success(['already-running', $realUrl]);
            }
        }

        // is home
        $home = false;
        $home_url = rtrim(home_url(), '/');
        $realUrl_stripped = preg_replace('#^https?://#', '', $realUrl);
        $home_url_stripped = preg_replace('#^https?://#', '', $home_url);

        if ($home_url_stripped == $realUrl_stripped) {
            $home = true;
        }

        // Set as Running
        set_transient('wpc_critical_ajax_' . $postID, 'true', 60);

        $requests = new wps_ic_requests();

        if (!empty($home)) {
            $args = ['url' => $realUrl . '?criticalCombine=true&testCompliant=true', 'version' => self::$version, 'async' => 'false', 'dbg' => 'true', 'hash' => time() . mt_rand(100, 9999), 'apikey' => get_option(WPS_IC_OPTIONS)['api_key']];
            $call = $requests->GET(self::$CRITICAL_URL_HOME, $args, ['timeout' => 2, 'blocking' => false]);
        } else {
            $args = ['url' => $realUrl . '?criticalCombine=true&testCompliant=true', 'home' => $home_url, 'version' => self::$version, 'async' => 'false', 'dbg' => 'true', 'hash' => time() . mt_rand(100, 9999), 'apikey' => get_option(WPS_IC_OPTIONS)['api_key']];
            $call = $requests->GET(self::$API_URL, $args, ['timeout' => 3, 'blocking' => false]);
        }

        wp_send_json_success('sent');
    }

    public function wps_fetchInitialTest()
    {
        $initialPageSpeedScore = get_option(WPS_IC_LITE_GPS);
        if (!empty($initialPageSpeedScore) && !empty($initialPageSpeedScore['result'])) {
            wp_send_json_success('done');
        }

        wp_send_json_error('not-done ' . print_r($initialPageSpeedScore, true));
    }


    public function custom_merge(array $array1, array $array2)
    {
        $result = $array1;

        foreach ($array2 as $key => $value) {
            if (is_array($value) && isset($result[$key]) && is_array($result[$key])) {
                // Recursively merge nested arrays
                $result[$key] = $this->custom_merge($result[$key], $value);
            } elseif (!isset($result[$key])) {
                // Add keys from $array2 only if they don't exist in $array1
                $result[$key] = $value;
            }
        }

        return $result;
    }

    /**
     * Change Settings Value
     */
    public function wps_ic_settings_change()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        global $wps_ic;

        $what = sanitize_text_field($_POST['what']);
        $value = sanitize_text_field($_POST['value']);
        $checked = sanitize_text_field($_POST['checked']);
        $checkbox = sanitize_text_field($_POST['checkbox']);


        $options = new wps_ic_options();
        $settings = $options->get_settings();

        if ($what == 'thumbnails') {
            if (!isset($value) || empty($value)) {
                $settings['thumbnails'] = [];
            } else {
                $settings['thumbnails'] = [];
                $value = rtrim($value, ',');
                $value = explode(',', $value);
                foreach ($value as $i => $thumb_size) {
                    $settings['thumbnails'][$thumb_size] = 1;
                }
            }
        } else {
            if ($what == 'autopilot') {
                if ($checked == 'checked') {
                } else {
                    $settings['otto'] = 'automated';
                }
            }

            if ($checkbox == 'true') {
                if ($checked === 'false') {
                    $settings[$what] = 0;
                } else {
                    $settings[$what] = 1;
                }
            } else {
                $settings[$what] = $value;
            }
        }

        if ($what == 'live_autopilot') {
            if ($value == '1') {
                // Enabline Live, clear local queue
                delete_option('wps_ic_bg_stop');
                delete_option('wps_ic_bg_process_stop');
                delete_option('wps_ic_bg_stopping');
                delete_option('wps_ic_bg_process');
                delete_option('wps_ic_bg_process_done');
                delete_option('wps_ic_bg_process_running');
                delete_option('wps_ic_bg_process_stats');
                delete_option('wps_ic_bg_last_run_compress');
                delete_option('wps_ic_bg_last_run_restore');
            }
        } elseif ($what == 'css' || $what == 'js') {
            // Purge CSS/JS Cache
            $this->purge_cdn_assets();
        }

        self::$cacheIntegrations->purgeAll();

        update_option(WPS_IC_SETTINGS, $settings);

        wp_send_json_success();
    }

    public function purge_cdn_assets()
    {
        $options = get_option(WPS_IC_OPTIONS);

        $call = self::$Requests->GET(WPS_IC_KEYSURL, ['action' => 'cdn_purge', 'domain' => site_url(), 'apikey' => $options['api_key']]);

        if (!empty($call)) {
            if ($call->success == 'true') {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    public function wps_ic_ajax_checkbox()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $setting_name = sanitize_text_field($_POST['setting_name']);
        $setting_value = sanitize_text_field($_POST['value']);
        $setting_checked = sanitize_text_field($_POST['checked']);

        $settings = get_option(WPS_IC_SETTINGS);

        $value = ($setting_checked == 'false') ? '0' : '1';

        // Parse "options[key]" or "options[key1][key2]" format from HTML name attribute
        preg_match_all('/\[([^\]]+)\]/', $setting_name, $matches);
        $keys = !empty($matches[1]) ? $matches[1] : [$setting_name];

        if (count($keys) === 2) {
            $settings[$keys[0]][$keys[1]] = $value;
        } else {
            $settings[$keys[0]] = $value;
        }

        if ($settings['live-cdn'] == '0') {
            $settings['js'] = '0';
            $settings['css'] = '0';
        }

        update_option(WPS_IC_SETTINGS, $settings);

        self::purgeBreeze();
        self::purge_cache_files();

        // Clear cache.
        if (function_exists('rocket_clean_domain')) {
            rocket_clean_domain();
        }

        // Lite Speed
        if (defined('LSCWP_V')) {
            do_action('litespeed_purge_all');
        }

        // HummingBird
        if (defined('WPHB_VERSION')) {
            do_action('wphb_clear_page_cache');
        }

        wp_send_json_success(['new_value' => $value, 'setting_name' => $setting_name, 'value' => $setting_value]);
    }

    /**
     * @return void
     */
    public static function purgeBreeze()
    {
        if (defined('BREEZE_VERSION')) {
            global $wp_filesystem;
            require_once(ABSPATH . 'wp-admin/includes/file.php');

            WP_Filesystem();

            $cache_path = breeze_get_cache_base_path(is_network_admin(), true);
            $wp_filesystem->rmdir(untrailingslashit($cache_path), true);

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

    /**
     * @return bool
     */
    public static function purge_cache_files()
    {
        $cache_dir = WPS_IC_CACHE;

        self::removeDirectory($cache_dir);

        return true;
    }

    /**
     * TODO: Remove?
     *
     * @param $path
     *
     * @return void
     */
    public static function removeDirectory($path)
    {
        $path = rtrim($path, '/');
        $files = glob($path . '/*');
        if (!empty($files)) {
            foreach ($files as $file) {
                is_dir($file) ? self::removeDirectory($file) : unlink($file);
            }
        }
    }

    public function wps_ic_dismiss_notice()
    {
        $notice_dismiss_info = get_option('wps_ic_notice_info');
        $tag = sanitize_text_field($_POST['id']);

        if (!empty ($tag)) {
            $notice_dismiss_info[$tag] = 0;
            update_option('wps_ic_notice_info', $notice_dismiss_info);
            wp_send_json_success();
        }
        wp_send_json_error();

    }

    public function wps_ic_fix_notice()
    {
        $plugin = sanitize_text_field($_POST['plugin']);
        $setting = sanitize_text_field($_POST['setting']);

        if (!empty($plugin) && !empty($setting)) {
            $integrations = new wps_ic_integrations();
            $fix = $integrations->fix($plugin, $setting);

            if ($fix) {
                wp_send_json_success();
            }
        }
        wp_send_json_error();

    }

    /**
     * @return void
     */
    public function wps_ic_get_setting()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $option_name = sanitize_text_field($_POST['name']);
        $option_subset = sanitize_text_field($_POST['subset']);

        if (!in_array($option_name, ['wpc-excludes', 'wpc-inline', 'wpc-url-excludes'])) {
            wp_send_json_error('Forbidden.');
        }

        $option = get_option($option_name);
        $value = $option[$option_subset];
        $default_excludes = $option[$option_subset . '_default_excludes_disabled'];
        $exclude_themes = $option[$option_subset . '_exclude_themes'];
        $exclude_plugins = $option[$option_subset . '_exclude_plugins'];
        $exclude_wp = $option[$option_subset . '_exclude_wp'];
        $exclude_third = $option[$option_subset . '_exclude_third'];
        $min_mobile_width = get_option('wpc-min-mobile-width');

        if (empty($value)) {
            $value = '';
        } else {
            $value = implode("\n", $value);
        }

        wp_send_json_success(['value' => $value, 'default_excludes' => $default_excludes, 'exclude_themes' => $exclude_themes, 'exclude_plugins' => $exclude_plugins, 'exclude_wp' => $exclude_wp, 'exclude_third' => $exclude_third, 'min_mobile_width' => $min_mobile_width]);
    }

    public function wps_ic_save_excludes_settings()
    {

        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $setting_name = sanitize_text_field($_POST['setting_name']);
        $setting_group = sanitize_text_field($_POST['group_name']);

        if ($setting_group == 'wpc-url-excludes') {
            //To be used in excluding url from an optimization option
            $excludes = $_POST['excludes'];
            $excludes = rtrim($excludes, "\n");
            $excludes = explode("\n", $excludes);


            $wpc_excludes = get_option($setting_group);
            $wpc_excludes[$setting_name] = $excludes;

            $updated = update_option($setting_group, $wpc_excludes);
        } elseif ($setting_group == 'wpc-excludes' || $setting_group == 'wpc-inline') {
            $excludes = $_POST['excludes'];
            $excludes = rtrim($excludes, "\n");
            $excludes = explode("\n", $excludes);
            $excludes = array_filter($excludes, 'trim');

            $default_enabled = sanitize_text_field($_POST['default_enabled']);
            $exclude_themes = sanitize_text_field($_POST['exclude_themes']);
            $exclude_plugins = sanitize_text_field($_POST['exclude_plugins']);
            $exclude_wp = sanitize_text_field($_POST['exclude_wp']);
            $exclude_third = sanitize_text_field($_POST['exclude_third']);
            $min_mobile_width = sanitize_text_field($_POST['min_mobile_width']);


            $wpc_excludes = get_option($setting_group);
            $wpc_excludes[$setting_name] = $excludes;
            $wpc_excludes[$setting_name . '_default_excludes_disabled'] = $default_enabled;
            $wpc_excludes[$setting_name . '_exclude_themes'] = $exclude_themes;
            $wpc_excludes[$setting_name . '_exclude_plugins'] = $exclude_plugins;
            $wpc_excludes[$setting_name . '_exclude_wp'] = $exclude_wp;
            $wpc_excludes[$setting_name . '_exclude_third'] = $exclude_third;

            // JS Delay popup saves both tabs at once: excludes + configure (lastLoadScript + deferScript)
            if (isset($_POST['lastLoadScript'])) {
                $lastLoad = sanitize_textarea_field($_POST['lastLoadScript']);
                $lastLoad = rtrim($lastLoad, "\n");
                $lastLoad = explode("\n", $lastLoad);
                $lastLoad = array_filter($lastLoad, 'trim');
                $wpc_excludes['lastLoadScript'] = $lastLoad;
            }

            if (isset($_POST['deferScript'])) {
                $defer = sanitize_textarea_field($_POST['deferScript']);
                $defer = rtrim($defer, "\n");
                $defer = explode("\n", $defer);
                $defer = array_filter($defer, 'trim');
                $wpc_excludes['deferScript'] = $defer;
            }

            if ($min_mobile_width !== 'false') {
                $updated1 = update_option('wpc-min-mobile-width', $min_mobile_width);
            }

            $updated2 = update_option($setting_group, $wpc_excludes);

            $updated = $updated1 || $updated2;
        } else {
            wp_send_json_error('Forbidden.');
        }


        if ($updated) {
            $cache = new wps_ic_cache_integrations();
            $cache::purgeAll();

            if ($setting_name == 'combine_js' || $setting_name == 'css_combine' || $setting_name == 'delay_js') {
                $cache::purgeCombinedFiles();
            }

            if ($setting_name == 'critical_css') {
                $cache::purgeCriticalFiles();
            }


        }


        wp_send_json_success($wpc_excludes);

    }

    /**
     * @return void
     */
    public function wps_ic_critical_run()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $criticalCSS = new wps_criticalCss();
        $criticalCSS->sendCriticalUrl('', $_POST['pageID']);
        wp_send_json_success();
    }

    public function wps_ic_pull_stats()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $options = get_option(WPS_IC_OPTIONS);

        self::$Requests->GET(WPS_IC_KEYSURL, ['apikey' => $options['api_key'], 'action' => 'pullStats']);
        wp_send_json_success();
    }

    /**
     * @return void
     */
    public function wps_ic_critical_get_assets()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $criticalCSS = new wps_criticalCss();
        $count = $criticalCSS->sendCriticalUrlGetAssets('', $_POST['pageID']);
        wp_send_json_success($count);
    }

    /**
     * @return void
     */
    public function wps_ic_ajax_v2_checkbox()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $options = get_option(WPS_IC_SETTINGS);

        $optionName = sanitize_text_field($_POST['optionName']);
        $optionValue = sanitize_text_field($_POST['optionValue']);

        $optionName = explode(',', $optionName);

        // CF settings are stored in WPS_IC_CF['settings'], not WPS_IC_SETTINGS
        if (is_array($optionName) && count($optionName) > 1 && $optionName[0] === 'cf') {
            $cf = get_option(WPS_IC_CF);
            if (!empty($cf)) {
                if (!isset($cf['settings'])) {
                    $cf['settings'] = ['assets' => '1', 'edge-cache' => 'all', 'cdn' => '1'];
                }
                $newValue = $cf['settings'][$optionName[1]] = $optionValue;
                update_option(WPS_IC_CF, $cf);
            } else {
                $newValue = $optionValue;
            }
        } elseif (is_array($optionName) && count($optionName) > 1) {
            $newValue = $options[$optionName[0]][$optionName[1]] = $optionValue;

            // Recalculate live-cdn when any serve option changes
            if ($optionName[0] === 'serve') {
                $cdnOn = false;
                $imageServeKeys = ['jpg', 'png', 'gif', 'svg'];
                if (isset($options['serve'])) {
                    foreach ($imageServeKeys as $k) {
                        if (!empty($options['serve'][$k]) && $options['serve'][$k] == '1') { $cdnOn = true; break; }
                    }
                }
                if (!$cdnOn && !empty($options['css']) && $options['css'] == '1') $cdnOn = true;
                if (!$cdnOn && !empty($options['js']) && $options['js'] == '1') $cdnOn = true;
                if (!$cdnOn && !empty($options['fonts']) && $options['fonts'] == '1') $cdnOn = true;
                $options['live-cdn'] = $cdnOn ? '1' : '0';
            }

            update_option(WPS_IC_SETTINGS, $options);
        } else {
            $optionName = $optionName[0];
            $newValue = $options[$optionName] = $optionValue;

            // Auto-scan homepage when Local font hosting is first enabled
            if ($optionName === 'replace-fonts' && $newValue === 'local') {
                $fontsMap = get_option(WPS_IC_FONTS_MAP);
                if (empty($fontsMap)) {
                    $fonts = new wps_ic_fonts();
                    $response = $fonts->callAPI(site_url());
                    $found = $fonts->scanForFonts($response);
                    $hasGoogleFonts = !empty($found['googleFontsStylesheets']) || !empty($found['gstaticUrls']);
                    if ($hasGoogleFonts) {
                        $fonts->readGoogleStylesheet($found);
                    }
                }
            }

            // Recalculate live-cdn when css, js, or fonts changes
            if (in_array($optionName, ['css', 'js', 'fonts'])) {
                $cdnOn = false;
                $imageServeKeys = ['jpg', 'png', 'gif', 'svg'];
                if (!empty($options['serve'])) {
                    foreach ($imageServeKeys as $k) {
                        if (!empty($options['serve'][$k]) && $options['serve'][$k] == '1') { $cdnOn = true; break; }
                    }
                }
                if (!$cdnOn && !empty($options['css']) && $options['css'] == '1') $cdnOn = true;
                if (!$cdnOn && !empty($options['js']) && $options['js'] == '1') $cdnOn = true;
                if (!$cdnOn && !empty($options['fonts']) && $options['fonts'] == '1') $cdnOn = true;
                $options['live-cdn'] = $cdnOn ? '1' : '0';
            }

            update_option(WPS_IC_SETTINGS, $options);
        }

        wp_send_json_success(['newValue' => $newValue, 'optionName' => $optionName]);
    }

    /**
     * Batch save — applies all checkbox changes in a single DB read/write cycle.
     * Fixes race condition where parallel per-checkbox AJAX calls overwrite each other.
     */
    public function wps_ic_ajax_v2_checkbox_batch()
    {
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $changes = json_decode(stripslashes($_POST['changes']), true);
        if (empty($changes) || !is_array($changes)) {
            wp_send_json_error(['message' => 'No changes provided']);
            wp_die();
        }

        $options = get_option(WPS_IC_SETTINGS);
        $cf = null;
        $serveChanged = false;
        $assetChanged = false;

        foreach ($changes as $change) {
            $optionName = explode(',', sanitize_text_field($change['name']));
            $optionValue = sanitize_text_field($change['value']);

            if (count($optionName) > 1 && $optionName[0] === 'cf') {
                // CF settings stored in WPS_IC_CF['settings']
                if ($cf === null) {
                    $cf = get_option(WPS_IC_CF);
                    if (!empty($cf) && !isset($cf['settings'])) {
                        $cf['settings'] = ['assets' => '1', 'edge-cache' => 'all', 'cdn' => '1'];
                    }
                }
                if (!empty($cf)) {
                    $cf['settings'][$optionName[1]] = $optionValue;
                }
            } elseif (count($optionName) > 1) {
                $options[$optionName[0]][$optionName[1]] = $optionValue;
                if ($optionName[0] === 'serve') $serveChanged = true;
            } else {
                $name = $optionName[0];
                $options[$name] = $optionValue;
                if (in_array($name, ['css', 'js', 'fonts'])) $assetChanged = true;

                // Auto-scan homepage when Local font hosting is first enabled
                if ($name === 'replace-fonts' && $optionValue === 'local') {
                    $fontsMap = get_option(WPS_IC_FONTS_MAP);
                    if (empty($fontsMap)) {
                        $fonts = new wps_ic_fonts();
                        $response = $fonts->callAPI(site_url());
                        $found = $fonts->scanForFonts($response);
                        $hasGoogleFonts = !empty($found['googleFontsStylesheets']) || !empty($found['gstaticUrls']);
                        if ($hasGoogleFonts) {
                            $fonts->readGoogleStylesheet($found);
                        }
                    }
                }
            }
        }

        // Recalculate live-cdn ONCE with all changes applied
        if ($serveChanged || $assetChanged) {
            $cdnOn = false;
            // Only check image serve keys (css/js/fonts in serve array are stale legacy values)
            $imageServeKeys = ['jpg', 'png', 'gif', 'svg'];
            if (!empty($options['serve'])) {
                foreach ($imageServeKeys as $k) {
                    if (!empty($options['serve'][$k]) && $options['serve'][$k] == '1') { $cdnOn = true; break; }
                }
            }
            if (!$cdnOn && !empty($options['css']) && $options['css'] == '1') $cdnOn = true;
            if (!$cdnOn && !empty($options['js']) && $options['js'] == '1') $cdnOn = true;
            if (!$cdnOn && !empty($options['fonts']) && $options['fonts'] == '1') $cdnOn = true;
            $options['live-cdn'] = $cdnOn ? '1' : '0';
        }

        update_option(WPS_IC_SETTINGS, $options);

        if ($cf !== null) {
            update_option(WPS_IC_CF, $cf);
        }

        wp_send_json_success(['saved' => count($changes)]);
    }

    /**
     * Purge all caches — called as separate AJAX request after settings are saved.
     */
    public function wps_ic_purge_after_save()
    {
        if (!current_user_can('manage_wpc_settings') || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error('Forbidden.');
        }

        // Settings that affect cached HTML output
        $htmlPurgeKeys = [
            // Fonts
            'replace-fonts', 'font-display', 'icon-font-display',
            'preload-crit-fonts', 'fontawesome-lazy',
            // CDN file types
            'css', 'js', 'fonts', 'lazy',
            'serve,jpg', 'serve,png', 'serve,gif', 'serve,svg',
            // Image optimization (changes CDN URL parameters in HTML)
            'generate_adaptive', 'generate_webp', 'retina', 'background-sizing',
            'qualityLevel',
            // Performance
            'critical,css', 'delay,js',
            'minify,html', 'minify,css', 'minify,js',
            // CF (changes CDN routing/URLs)
            'cf,cdn', 'cf,assets',
        ];
        // Settings that affect Critical CSS content (CSS-only changes)
        $critPurgeKeys = ['replace-fonts', 'font-display', 'icon-font-display', 'preload-crit-fonts', 'css', 'fonts', 'minify,css', 'critical,css'];

        $changedKeys = !empty($_POST['changed_keys']) ? array_map('sanitize_text_field', (array) $_POST['changed_keys']) : [];

        $needsHtmlPurge = !empty($changedKeys) && !empty(array_intersect($changedKeys, $htmlPurgeKeys));
        $needsCritPurge = !empty($changedKeys) && !empty(array_intersect($changedKeys, $critPurgeKeys));

        if ($needsHtmlPurge) {
            // Purge WPC HTML cache + bump ICV hashes
            delete_transient('wps_ic_css_cache');
            delete_option('wps_ic_modified_css_cache');
            delete_option('wps_ic_css_combined_cache');
            $cache = new wps_ic_cache_integrations();
            $cache::purgeAll(false, true, false, false, true);
            $cache::purgeCombinedFiles();

            // Purge third-party caches
            self::purgeBreeze();
            self::purge_cache_files();
            if (function_exists('rocket_clean_domain')) rocket_clean_domain();
            if (defined('LSCWP_V')) do_action('litespeed_purge_all');
            if (defined('WPHB_VERSION')) do_action('wphb_clear_page_cache');
        }

        if ($needsCritPurge) {
            // Purge WPC Critical CSS
            global $wpdb;
            $options_table = $wpdb->options;
            $wpdb->query($wpdb->prepare("DELETE FROM $options_table WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like('_transient_wpc_critical_key_') . '%', $wpdb->esc_like('_transient_timeout_wpc_critical_key_') . '%'));
            if (!isset($cache)) $cache = new wps_ic_cache_integrations();
            $cache::purgeCriticalFiles();
        }

        wp_send_json_success();
    }

    /**
     * @return void
     * @since 5.20.01
     */
    public function wps_ic_generate_critical_css()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $options = get_option(WPS_IC_OPTIONS);

        if (empty($options['api_key'])) {
            wp_send_json_error('API Key empty!');
        }

        $criticalCSS = new wps_criticalCss($_SERVER['HTTP_REFERER']);
        $criticalCSS->generateCriticalAjax();

        wp_send_json_success();
    }

    /**
     * @return void
     * @since 5.20.01
     */
    public function wps_ic_preload_page()
    {
        $options = get_option(WPS_IC_OPTIONS);

        if (empty($options['api_key'])) {
            wp_send_json_error('API Key empty!');
        }

        $url = WPS_IC_PRELOADER_API_URL;

        self::$Requests->POST($url, ['single_url' => $_SERVER['HTTP_REFERER'], 'apikey' => $options['api_key']]);

        sleep(3);

        wp_send_json_success();
    }

    /**
     * @return void
     * @since 5.20.01
     */
    public function wps_ic_purge_html()
    {
        if ((!current_user_can('manage_wpc_settings') && !current_user_can('manage_wpc_purge')) || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $options = get_option(WPS_IC_OPTIONS);

        if (empty($options['api_key'])) {
            wp_send_json_error('API Key empty!');
        }

        delete_transient('wps_ic_css_cache');
        delete_option('wps_ic_modified_css_cache');
        delete_option('wps_ic_css_combined_cache');

        $cache = new wps_ic_cache_integrations();
        $cache::purgeAll(false, true, false, false, true);

        // Todo: maybe remove?
        $cache::purgeCombinedFiles();

        set_transient('wps_ic_purging_cdn', 'true', 30);

        sleep(3);
        delete_transient('wps_ic_purging_cdn');
        wp_send_json_success();
    }

    public function wpc_purgeCF($return = false)
    {
        $cfSettings = get_option(WPS_IC_CF);

        if (!empty($cfSettings)) {
            $zone = $cfSettings['zone'];
            $cfapi = new WPC_CloudflareAPI($cfSettings['token']);
            if ($cfapi) {
                $cfapi->purgeCache($zone);
                sleep(6);
            }
        }

        if ($return) {
            return true;
        }

        wp_send_json_success();
    }

    /**
     * @return void
     * @since 5.20.01
     */
    public function wps_ic_purge_critical_css()
    {
        if ((!current_user_can('manage_wpc_settings') && !current_user_can('manage_wpc_purge')) || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $options = get_option(WPS_IC_OPTIONS);

        if (empty($options['api_key'])) {
            wp_send_json_error('API Key empty!');
        }

        // Delete Transient for Critical Lock
        global $wpdb;

        // Get the correct options table name with prefix
        $options_table = $wpdb->options;

        // Delete transient values
        $wpdb->query($wpdb->prepare("DELETE FROM $options_table WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like('_transient_wpc_critical_key_') . '%', $wpdb->esc_like('_transient_timeout_wpc_critical_key_') . '%'));

        delete_transient('wps_ic_css_cache');
        delete_option('wps_ic_modified_css_cache');
        delete_option('wps_ic_css_combined_cache');

        $cache = new wps_ic_cache_integrations();
        $cache::purgeCriticalFiles();
        $cache::purgeAll(false, false, false, false);

        set_transient('wps_ic_purging_cdn', 'true', 30);

        sleep(3);
        delete_transient('wps_ic_purging_cdn');
        wp_send_json_success();
    }

    /**
     * @return void
     * @since 5.20.01
     */
    public function wps_ic_purge_cdn()
    {
        if ((!current_user_can('manage_wpc_settings') && !current_user_can('manage_wpc_purge')) || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $oldOptions = $options = get_option(WPS_IC_OPTIONS);

        if (empty($options['api_key'])) {
            wp_send_json_error('API Key empty!');
        }

        $cache = new wps_ic_cache_integrations();
        $cache::purgeCriticalFiles();
        $cache::purgeAll();

        $CSSHash = substr(md5(microtime(true)), 0, 6);
        $JSHash = strrev($CSSHash);

        $options['css_hash'] = $CSSHash;
        $options['js_hash'] = $JSHash;

        if (!class_exists('wps_ic_log')) {
            include_once WPS_IC_DIR . 'classes/log.class.php';
        }

        $log = new wps_ic_log();
        $log->logCachePurging($oldOptions, $options, 'wps_ic_purge_cdn');

        update_option(WPS_IC_OPTIONS, $options);

        delete_transient('wps_ic_css_cache');
        delete_option('wps_ic_modified_css_cache');
        delete_option('wps_ic_css_combined_cache');

        set_transient('wps_ic_purging_cdn', 'true', 30);

        $call = self::$Requests->GET(WPS_IC_KEYSURL, ['action' => 'cdn_purge', 'apikey' => $options['api_key']]);

        sleep(3);
        delete_transient('wps_ic_purging_cdn');
        wp_send_json_success();

        // Ignore this below, we just do a trigger
        if (!empty($call)) {
            if ($call->success == 'true') {
                delete_transient('wps_ic_purging_cdn');
                wp_send_json_success();
            }
        }

        wp_send_json_error('Could not call purge action!');
    }

    /**
     * Exclude the image
     * @since 4.0.0
     */
    public function wps_ic_exclude_live()
    {
        global $wps_ic;

        $output = '';
        $action = sanitize_text_field($_POST['do_action']);
        $attachment_id = sanitize_text_field($_POST['attachment_id']);
        $filedata = get_attached_file($attachment_id);
        $basename = sanitize_title(basename($filedata));
        $exclude_list = get_option('wps_ic_exclude_list');

        if (!$exclude_list) {
            $exclude_list = [];
        }

        $exclude = get_post_meta($attachment_id, 'wps_ic_exclude_live', true);

        $filedata = get_attached_file($attachment_id);

        // Get scaled file size
        $filesize = filesize($filedata);
        $wpScaledFilesize = wps_ic_format_bytes($filesize, null, null, false);

        // Get original filesize
        $originalFilepath = wp_get_original_image_path($attachment_id);
        $originalFilesize = filesize($originalFilepath);
        $filesize = wps_ic_format_bytes($originalFilesize, null, null, false);

        if ($action == 'exclude') {
            $exclude_list[$attachment_id] = $basename;
            update_post_meta($attachment_id, 'wps_ic_exclude_live', 'true');

            $output .= '<div class="wps-ic-compressed-logo">';
            $output .= '<img src="' . self::$logo_excluded . '" />';
            $output .= '</div>';

            $output .= '<div class="wps-ic-compressed-info">';

            $output .= '<div class="wpc-info-box">';
            $output .= '<h5>Excluded</h5>';
            $output .= '</div>';

            $output .= '<div>';
            $output .= '<ul class="wpc-inline-list">';

            $output .= '<li><div class="wpc-savings-tag">' . $filesize . '</div></li>';

            $output .= '<li>';
            $output .= '<a class="wpc-dropdown-btn wps-ic-include-live ic-tooltip" title="Include" data-action="include" data-attachment_id="' . $attachment_id . '"></a>';
            $output .= '</li>';

            $output .= '</ul>';
            $output .= '</div>';

            $output .= '</div>';
        } else {
            unset($exclude_list[$attachment_id]);
            delete_post_meta($attachment_id, 'wps_ic_exclude_live');

            $output .= '<div class="wps-ic-compressed-logo">';
            $output .= '<img src="' . self::$logo_uncompressed . '" />';
            $output .= '</div>';

            $output .= '<div class="wps-ic-compressed-info">';

            $output .= '<div class="wpc-info-box">';
            $output .= '<h5>Not Compressed</h5>';
            $output .= '</div>';

            $output .= '<div>';
            $output .= '<ul class="wpc-inline-list">';

            $output .= '<li><div class="wpc-savings-tag">' . $filesize . '</div></li>';

            $output .= '<li>';
            $output .= '<a class="wpc-dropdown-btn wps-ic-compress-live ic-tooltip" title="Compress" data-attachment_id="' . $attachment_id . '"></a>';
            $output .= '</li>';
            $output .= '<li>';
            $output .= '<a class="wpc-dropdown-btn wps-ic-exclude-live ic-tooltip" title="Exclude" data-action="exclude" data-attachment_id="' . $attachment_id . '"></a>';
            $output .= '</li>';

            $output .= '</ul>';
            $output .= '</div>';

            $output .= '</div>';
        }

        update_option('wps_ic_exclude_list', $exclude_list);
        wp_send_json_success(['html' => $output]);
    }

    /**
     * Exclude the image
     * @since 4.0.0
     */
    public function wps_ic_simple_exclude_image()
    {
        global $wps_ic;
        $wps_ic = new wps_ic_compress();
        $wps_ic->simple_exclude($_POST, 'html');
    }

    /**
     * Connect Multsites With API
     */
    public function wps_ic_api_mu_connect()
    {
        global $wps_ic;

        // Is localhost?
        $sites = get_sites();

        // API Key
        $apikey = sanitize_text_field($_POST['apikey']);
        $affiliate_code = get_option('wps_ic_affiliate_code');

        if ($sites && is_multisite()) {
            $error = false;

            foreach ($sites as $key => $site) {

                $call = self::$Requests->GET(WPS_IC_KEYSURL, ['action' => 'connect', 'apikey' => $apikey, 'site' => urlencode($site->domain . $site->path), 'affiliate_code' => $affiliate_code]);

                if (!empty($call)) {

                    if ($call->success && $call->data->api_key != '' && $call->data->response_key != '') {
                        $options = new wps_ic_options();
                        $options->set_option('api_key', $call->data->api_key);
                        $options->set_option('response_key', $call->data->response_key);
                        $options->set_option('orp', $call->data->orp);

                        $settings = get_option(WPS_IC_SETTINGS);

                        $sizes = get_intermediate_image_sizes();
                        foreach ($sizes as $key => $value) {
                            $settings['thumbnails'][$value] = 1;
                        }

                        update_option(WPS_IC_SETTINGS, $settings);
                    }
                } else {
                    $error = true;
                }
            }

            if ($error) {
                wp_send_json_error();
            } else {
                wp_send_json_success();
            }
        }

        wp_send_json_error('0');
    }


    /**
     * Lite Connect
     */
    public function wps_lite_connect()
    {
        $connect = new wps_ic_connect();
        $call = $connect->connectLite();
    }


    public function wpsRemoveFont()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $fontId = sanitize_text_field($_POST['fontId']);

        $font = new wps_ic_fonts();
        $font->removeFont($fontId);

        wp_send_json_success();
    }


    public function wpsScanFonts()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $url = sanitize_url($_POST['scanUrl']);
        if (empty($url)) {
            wp_send_json_error('No URL provided.');
        }

        $fonts = new wps_ic_fonts();
        $response = $fonts->callAPI($url);
        $found = $fonts->scanForFonts($response);

        $hasGoogleFonts = !empty($found['googleFontsStylesheets']) || !empty($found['gstaticUrls']);

        if ($hasGoogleFonts) {
            $fonts->readGoogleStylesheet($found);
        }

        wp_send_json_success(['found' => $hasGoogleFonts]);
    }

    public function wpsChangeGui()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $view = sanitize_text_field($_POST['view']);
        update_option(WPS_IC_GUI, $view);
        update_option('wpsShowAdvanced', 'true');
        wp_send_json_success();
    }


    /**
     * Connect With API
     */
    public function wps_ic_live_connect()
    {
        $connect = new wps_ic_connect();
        $call = $connect->connect();
    }

    /**
     * Deauthorize site with remote api
     */
    public function wps_ic_deauthorize_api()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        global $wps_ic;

        // Vars
        $site = site_url();
        $options = new wps_ic_options();
        $apikey = $options->get_option('api_key');

        // Verify API Key is our database and user has is confirmed getresponse
        self::$Requests->GET(WPS_IC_KEYSURL, ['action' => 'disconnect', 'apikey' => $apikey, 'site' => urlencode($site)]);

        $options->set_option('api_key', '');
        $options->set_option('response_key', '');
        $options->set_option('orp', '');
    }

    /**
     * Heartbeat
     */
    public function wps_ic_media_library_heartbeat()
    {
        global $wps_ic, $wpdb;
        $html = [];

        $like = $wpdb->esc_like('_transient_wps_ic_heartbeat_') . '%';

        $heartbeatData = $wpdb->get_results($wpdb->prepare("SELECT *
         FROM {$wpdb->options}
         WHERE option_name LIKE %s", $like));

        if (!$heartbeatData) {
            wp_send_json_error();
        }

        foreach ($heartbeatData as $transient) {
            $data = maybe_unserialize($transient->option_value);
            $imageID = $data['imageID'];
            $status = $data['status'];

            if ($status == 'compressed') {
                $html[$imageID] = $wps_ic->media_library->compress_details($imageID);
                delete_transient('wps_ic_compress_' . $imageID);
                delete_transient('wps_ic_heartbeat_' . $imageID);
            } elseif ($status == 'restored') {
                $html[$imageID] = $wps_ic->media_library->compress_details($imageID);
                delete_transient('wps_ic_compress_' . $imageID);
                delete_transient('wps_ic_heartbeat_' . $imageID);
            }
        }

        wp_send_json_success(['html' => $html]);
    }

    public function wps_ic_bulkRestoreHeartbeat()
    {
        $isDone = get_transient('wps_ic_bulk_done');
        $parsedImages = get_option('wps_ic_parsed_images');
        $bulkStatus = get_option('wps_ic_BulkStatus');

        $bulkProcess = get_option('wps_ic_bulk_process');
        if ($bulkProcess && $bulkProcess['status'] != 'restoring') {
            wp_send_json_error(['msg' => 'bulk-process-failed']);
        }


        if ($isDone) {
            $output = [];
            $bulkStatus = get_option('wps_ic_BulkStatus');
            $imagesInRestoreQueue = !empty($bulkStatus['foundImageCount']) ? $bulkStatus['foundImageCount'] : 0;
            $imagesRestored = !empty($bulkStatus['restoredImageCount']) ? $bulkStatus['restoredImageCount'] : 0;
            $progressBar = ($imagesInRestoreQueue > 0) ? round(($imagesRestored / $imagesInRestoreQueue) * 100) : 100;

            $output['status'] = 'done';
            $output['finished'] = $imagesRestored;
            $output['total'] = $imagesInRestoreQueue;
            $output['progress'] = $progressBar;

            delete_option('wps_ic_bulk_process');
            wp_send_json_success($output);
        }

        // Total Images in Restore Queue
        $imagesInRestoreQueue = !empty($bulkStatus['foundImageCount']) ? $bulkStatus['foundImageCount'] : 0;
        $imagesRestored = !empty($bulkStatus['restoredImageCount']) ? $bulkStatus['restoredImageCount'] : 0;


        // Not ready for output, nothing is done yet
        if (empty($parsedImages)) {
            wp_send_json_success(['status' => 'parsing', 'message' => 'We have found ' . $imagesInRestoreQueue . ($imagesInRestoreQueue == 1 ? ' image' : ' images') . ' to restore...']);
        }

        $progressBar = ($imagesInRestoreQueue > 0) ? round(($imagesRestored / $imagesInRestoreQueue) * 100) : 0;

        // Visual Patch so that user can see some progress
        if ($progressBar == 0) {
            $progressBar = 3;
        }

        // Bugfix, remove total index
        $onlyImages = $parsedImages;
        unset($onlyImages['total']);

        $lastID = null;
        if (!empty($onlyImages)) {
            $lastID = array_key_last($onlyImages);
        }

        // If no images processed yet, return parsing status
        if ($lastID === null) {
            wp_send_json_success(['status' => 'parsing', 'message' => 'Restoring images...', 'finished' => $imagesRestored, 'total' => $imagesInRestoreQueue, 'progress' => $progressBar]);
        }

        $lastProgress = isset($_POST['lastProgress']) ? $_POST['lastProgress'] : 0;

        $output = [];
        $output['status'] = 'working';
        $output['parsedImages'] = $parsedImages;
        $output['html'] = $this->bulkRestoreHtml($lastID, $lastProgress);
        $output['finished'] = $imagesRestored;
        $output['total'] = $imagesInRestoreQueue;
        $output['progress'] = $progressBar;
        $output['parsedImage'] = isset($parsedImages[$lastID]) ? $parsedImages[$lastID] : [];

        if ($imagesRestored >= $imagesInRestoreQueue) {
            delete_option('wps_ic_bulk_process');
            set_transient('wps_ic_bulk_done', true, 60);
        }

        wp_send_json_success($output);
    }

    public function bulkRestoreHtml($imageID, $lastProgress = '')
    {
        $output = '';

        // Use wp_get_attachment_url (DB-only, works even while file is being restored)
        $thumbUrl = wp_get_attachment_url($imageID);
        if (empty($thumbUrl)) $thumbUrl = '';

        $image_full_filename = $thumbUrl ? basename($thumbUrl) : ('Image #' . $imageID);
        $filedata = get_attached_file($imageID);

        $originalPath = wp_get_original_image_path($imageID);
        $original_filesize = ($originalPath && file_exists($originalPath)) ? @filesize($originalPath) : 0;

        $output .= '<div class="wps-ic-bulk-html-wrapper">';

        $output .= '<div class="bulk-restore-container">';

        if ($thumbUrl) {
            $output .= '<div class="bulk-restore-preview-container">';
            $output .= '<div class="bulk-restore-preview-inner">';
            $output .= '<div class="bulk-restore-preview-image-holder">';
            $output .= '<div class="image-holder-inner">';
            $output .= '<div style="background-image:url(' . esc_url($thumbUrl) . ');" class="image-holder-bg"></div>';
        } else {
            $output .= '<div class="bulk-restore-preview-container">';
            $output .= '<div class="bulk-restore-preview-inner">';
            $output .= '<div class="bulk-restore-preview-image-holder">';
            $output .= '<div class="image-holder-inner" style="background:#f1f5f9;display:flex;align-items:center;justify-content:center;">';
            $output .= '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>';
        }
        $output .= '</div>';
        $output .= '</div>';
        $output .= '</div>';
        $output .= '</div>';

        $output .= '<div class="bulk-restore-info">';

        $output .= '<div class="bulk-restore-status-top-left">';
        $output .= '<span class="badge"><i class="icon icon-check"></i> Restored</span>';
        $output .= '</div>';

        $bulkStatus = get_option('wps_ic_BulkStatus');
        $restoredCount = !empty($bulkStatus['restoredImageCount']) ? $bulkStatus['restoredImageCount'] : 0;
        $totalCount = !empty($bulkStatus['foundImageCount']) ? $bulkStatus['foundImageCount'] : 0;
        $output .= '<div class="bulk-restore-status-top-right">';
        $output .= '<h3>' . $restoredCount . ' / ' . $totalCount . '</h3>';
        $output .= '<h5>Images Restored</h5>';
        $output .= '</div>';

        $output .= '<div class="bulk-restore-status-container">';
        $output .= '<h4>' . $image_full_filename . '</h4>';
        $sizeDisplay = $original_filesize > 0 ? wps_ic_format_bytes($original_filesize, null, null, false) : 'Restoring...';
        $output .= '<span><i class="restore-bullet"></i> ' . $sizeDisplay . '</span>';
        $output .= '<div class="bulk-status-progress-bar">
              <div class="progress-bar-outer">
                <div class="progress-bar-inner" style="width: ' . $lastProgress . '%;"></div>
              </div>
            </div>';
        $output .= '</div>';

        $output .= '</div>';

        $output .= '</div>';

        $output .= '</div>';

        return $output;
    }

    public function wps_ic_bulkCompressHeartbeat()
    {
        /***
         * Idea:
         * on every heartbeat check wps_ic_parsed_images and get the last inserted id
         * compare every last_inserted_id with last_shown_id (transient), if different,update stats
         */

        $bulkStatus = get_option('wps_ic_BulkStatus');
        $parsedImages = get_option('wps_ic_parsed_images');

        // Total Images Found
        $totalImagesFound = $bulkStatus['foundImageCount'];
        $totalThumbsFound = $bulkStatus['foundThumbCount'];
        $compressedImages = $bulkStatus['compressedImageCount'];

        // Bugfix, remove total index
        $onlyImages = $parsedImages;
        unset($onlyImages['total']); // remove total

        // Nothing done yet
        if (empty($onlyImages)) {
            wp_send_json_success(['status' => 'parsing', 'message' => 'We have found ' . $totalImagesFound . ($totalImagesFound == 1 ? ' image' : ' images') . ' to optimize...']);
        }

        if (!empty($onlyImages)) {
            $lastID = array_key_last($onlyImages);
        }

        // Stats for the last optimized image
        $stats = get_post_meta($lastID, 'ic_stats', true);
        $original_filesize = isset($stats['original']['original']['size']) ? $stats['original']['original']['size'] : 0;
        $compressed_filesize = isset($stats['original']['compressed']['size']) ? $stats['original']['compressed']['size'] : 0;

        // Check if negative savings
        $savedKB = wps_ic_format_bytes($original_filesize - $compressed_filesize) . ' Saved';
        if ($original_filesize <= $compressed_filesize) {
            $savedKB = 'No savings';
        }

        // HTML for the Last Optimized image
        $status = '<ul class="wps-icon-list">';
        $status .= '<li><i class="wps-icon saved"></i>' . $savedKB . '</li>';
        $status .= '<li><i class="wps-icon quality"></i> ' . ucfirst(self::$settings['optimization']) . ' Mode</li>';
        if (self::$settings['generate_webp'] == '1') {
            $status .= '<li><i class="wps-icon webp"></i> WebP Generated</li>';
        }
        $status .= '</ul>';

        // ProgressBar for overall Optimization (compressed images / total images to compress)
        $progressBar = ($totalImagesFound > 0) ? round(($compressedImages / $totalImagesFound) * 100) : 0;

        // Full Image Name
        $full = wp_get_original_image_url($lastID);
        $imageFileName = $full ? basename($full) : ('Image #' . $lastID);

        // Savings Calc
        $originalSize = isset($parsedImages['total']['original']) ? $parsedImages['total']['original'] : 0;
        $compressedSize = isset($parsedImages['total']['compressed']) ? $parsedImages['total']['compressed'] : 0;
        $imagesAndThumbs = (!empty($bulkStatus['compressedImageCount']) ? $bulkStatus['compressedImageCount'] : 0) + (!empty($bulkStatus['compressedThumbs']) ? $bulkStatus['compressedThumbs'] : 0);

        // Avg Savings
        $avgReduction = ($originalSize > 0 && $imagesAndThumbs > 0) ? (1 - ($compressedSize / $originalSize)) * 100 : 0;
        $avgReduction = number_format($avgReduction, 1);
        $avgReductionHTML = '<h3>' . $avgReduction . '%</h3><h5>Average Savings</h5>';

        // Total Savings
        $bulkSavings = wps_ic_format_bytes($originalSize - $compressedSize, null, null, false);
        $bulkSavingsHTML = '<h3>' . $bulkSavings . '</h3><h5>Total Savings</h5>';

        // Compressed Images
        $CompressedImagesHTML = '<h3>' . $compressedImages . '/' . $totalImagesFound . '</h3><h5>Original Images</h5>';
        $CompressedThumbsHTML = '<h3>' . $imagesAndThumbs . '/' . $totalThumbsFound . '</h3><h5>Total Images</h5>';

        $output['html'] = $this->bulkCompressHtml($lastID);
        $output['status'] = $status;
        $output['progress'] = $progressBar;
        $output['parsedImage'] = $parsedImages[$lastID];
        $output['lastFileName'] = $imageFileName;
        $output['progressAvgReduction'] = $avgReductionHTML;
        $output['progressTotalSavings'] = $bulkSavingsHTML;
        $output['progressCompressedImages'] = $CompressedImagesHTML;
        $output['progressCompressedThumbs'] = $CompressedThumbsHTML;

        if ($compressedImages >= $totalImagesFound) {
            delete_option('wps_ic_bulk_process');
            set_transient('wps_ic_bulk_done', true, 60);
        }

        $isDone = get_transient('wps_ic_bulk_done');
        if ($isDone) {
            $output = [];
            $output['status'] = 'done';
            delete_option('wps_ic_bulk_process');
            delete_transient('wps_ic_stuck_check');
            delete_option('wps_ic_bulk_counter');
            wp_send_json_success($output);
        }

        wp_send_json_success($output);
    }

    public function bulkCompressHtml($imageID)
    {
        $output = '';

        $thumbnail = wp_get_attachment_image_src($imageID, 'large');
        $full = wp_get_attachment_image_src($imageID, 'full');

        $image_filename = basename($thumbnail[0]);
        $image_full_filename = basename($full[0]);

        // Use the same savings data as single image (unscaled baseline → best variant)
        $ic_savings = get_post_meta($imageID, 'ic_savings', true);
        $ic_baseline = get_post_meta($imageID, 'ic_savings_baseline', true);
        $ic_saved_bytes = get_post_meta($imageID, 'ic_savings_bytes', true);

        // Before = unscaled original, After = baseline minus saved bytes
        if ($ic_baseline > 0) {
            $original_filesize = wps_ic_format_bytes($ic_baseline, null, null, false);
            $after_size = $ic_baseline - $ic_saved_bytes;
            $compressed_filesize = wps_ic_format_bytes($after_size, null, null, false);
            $savings = floatval($ic_savings);
        } else {
            // Fallback to ic_stats if ic_savings not yet available
            $stats = get_post_meta($imageID, 'ic_stats', true);
            if (empty($stats)) {
                $uploadfile = get_attached_file($imageID);
                $stats['original']['original']['size'] = @filesize($uploadfile) ?: 0;
                $stats['original']['compressed']['size'] = 0;
            }
            $original_filesize = wps_ic_format_bytes($stats['original']['original']['size'], null, null, false);
            $compressed_filesize = wps_ic_format_bytes($stats['original']['compressed']['size'], null, null, false);
            $savings = ($stats['original']['original']['size'] > 0 && $stats['original']['compressed']['size'] > 0)
                ? round((1 - ($stats['original']['compressed']['size'] / $stats['original']['original']['size'])) * 100, 1)
                : 0;
        }

        // Original image URL for the before preview
        $backup_images = get_post_meta($imageID, 'ic_backup_images', true);
        if (!empty($backup_images['full']) && !file_exists($backup_images['full'])) {
            $original_image = $thumbnail[0];
        } else {
            $original_image = $full[0];
        }

        $savingsHTML = '';
        if ($savings <= 0.9) {
            $savingsHTML = 'No further savings';
        } else {
            $savingsHTML = $savings . '% Savings';
        }

        $output .= '<div class="wps-ic-bulk-html-wrapper">';

        $output .= '<div class="wps-ic-bulk-header">';
        $output .= '<div class="wps-ic-bulk-before">';
        $output .= '<div class="image-holder">';

        $output .= '<div class="image-holder-inner">';
        $output .= '<div style="background-image:url(' . $original_image . ');" class="image-holder-bg"></div>';
        $output .= '</div>';

        $output .= '<div class="image-info-holder">';
        $output .= '<h4>Before</h4>';
        $output .= '<h3>' . $original_filesize . '</h3>';
        $output .= '</div>';

        $output .= '</div>';
        $output .= '</div>';
        $output .= '<div class="wps-ic-bulk-logo">';
        $output .= '<div class="logo-holder">';
        $output .= '<div class="wps-ic-bulk-preparing-logo-container">
        <div class="wps-ic-bulk-preparing-logo">
          <img src="' . WPS_IC_URI . 'assets/images/logo/blue-icon.svg" class="bulk-logo-prepare"/>
          <svg class="bulk-preparing" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="50" cy="50" r="30" stroke="var(--wpc-brand-bg, #eff7ff)" stroke-width="5" fill="none"></circle><circle cx="50" cy="50" r="30" stroke="var(--wpc-brand-primary, #4c89eb)" stroke-width="3" stroke-linecap="round" fill="none"><animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="2s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform><animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="2s" values="18.85 169.65;94.25 94.25;18.85 169.65" keyTimes="0;0.5;1"></animate></circle></svg>
        </div>
      </div>';
        $output .= '</div>';
        $output .= '<div class="wps-ic-percent-savings">';
        $output .= '<h3>' . $savingsHTML . '</h3>';
        $output .= '</div>';
        $output .= '<div class="wps-ic-bulk-loading">';
        $output .= '';
        $output .= '</div>';
        $output .= '</div>';
        $output .= '<div class="wps-ic-bulk-after">';
        $output .= '<div class="image-holder">';

        $output .= '<div class="image-holder-inner">';
        $output .= '<div style="background-image:url(' . $thumbnail[0] . ');" class="image-holder-bg"></div>';
        $output .= '</div>';

        $output .= '<div class="image-info-holder">';
        $output .= '<h4>After</h4>';
        $output .= '<h3>' . $compressed_filesize . '</h3>';
        $output .= '</div>';

        $output .= '</div>';
        $output .= '</div>';
        $output .= '</div>';

        $output .= '</div>';

        return $output;
    }

    public function wps_ic_StopBulk()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        global $wpdb;

        $local = new wps_ic_local();
        $send = $local->sendToAPI('stop');

        if ($send) {
            delete_option('wps_ic_parsed_images');
            delete_option('wps_ic_BulkStatus');
            delete_option('wps_ic_bulk_process');
            set_transient('wps_ic_bulk_done', true, 60);

            // Delete all transients
            $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('wps_ic_compress_') . '%'));

            wp_send_json_success();
        }
    }

    public function wps_ic_getBulkStats()
    {
        $bulkStatus = get_option('wps_ic_BulkStatus');
        $parsedImages = get_option('wps_ic_parsed_images');

        $output = '<div class="wps-ic-bulk-html-wrapper">';
        $output .= '<div class="wps-ic-bulk-header">';
        $output .= '<div class="wps-ic-bulk-logo">';

        $output .= '<div class="logo-holder">';
        $output .= '<img src="' . WPS_IC_URI . 'assets/images/bulk/compress-complete.svg' . '">';
        $output .= '</div>';

        if ($_POST['type'] == 'compress') {
            $output .= '<div class="wps-ic-percent-savings">';
            $output .= '<h2>Image Compression Complete!</h2>';
            $output .= '</div>';

            // Show stats for compress completion
            if (!empty($bulkStatus)) {
                $totalImages = !empty($bulkStatus['compressedImageCount']) ? $bulkStatus['compressedImageCount'] : 0;
                $totalThumbs = !empty($bulkStatus['compressedThumbs']) ? $bulkStatus['compressedThumbs'] : 0;
                $originalSize = !empty($bulkStatus['total']['original']['size']) ? $bulkStatus['total']['original']['size'] : 0;
                $compressedSize = !empty($bulkStatus['total']['compressed']['size']) ? $bulkStatus['total']['compressed']['size'] : 0;
                $savings = $originalSize - $compressedSize;
                $avgReduction = ($originalSize > 0 && $totalThumbs > 0) ? round((1 - ($compressedSize / $originalSize)) * 100, 1) : 0;

                $output .= '<div class="wpc-completion-stats">';

                $output .= '<div class="wpc-completion-stat">';
                $output .= '<div class="wpc-completion-icon"><img src="' . WPS_IC_URI . 'assets/icons/bulk/original-images.svg" /></div>';
                $output .= '<h3>' . $totalImages . '</h3><h5>Original Images</h5>';
                $output .= '</div>';

                $output .= '<div class="wpc-completion-stat">';
                $output .= '<div class="wpc-completion-icon"><img src="' . WPS_IC_URI . 'assets/icons/bulk/total-images.svg" /></div>';
                $output .= '<h3>' . $totalThumbs . '</h3><h5>Total Images</h5>';
                $output .= '</div>';

                $output .= '<div class="wpc-completion-stat">';
                $output .= '<div class="wpc-completion-icon"><img src="' . WPS_IC_URI . 'assets/icons/bulk/total-savings.svg" /></div>';
                $output .= '<h3>' . wps_ic_format_bytes($savings, null, null, false) . '</h3><h5>Total Savings</h5>';
                $output .= '</div>';

                $output .= '<div class="wpc-completion-stat">';
                $output .= '<div class="wpc-completion-icon"><img src="' . WPS_IC_URI . 'assets/icons/bulk/average-savings.svg" /></div>';
                $output .= '<h3>' . $avgReduction . '%</h3><h5>Average Savings</h5>';
                $output .= '</div>';

                $output .= '</div>';
            }
        } else {
            $output .= '<div class="wps-ic-percent-savings" style="margin-bottom:40px;">';
            $output .= '<h2>Image Restore Complete</h2>';
            $output .= '</div>';

            // Show stats for restore completion
            if (!empty($bulkStatus)) {
                $restoredCount = !empty($bulkStatus['restoredImageCount']) ? $bulkStatus['restoredImageCount'] : 0;
                $output .= '<div class="bulk-restore-status-progress" style="display:flex;justify-content:center;margin-top:10px;">';
                $output .= '<div class="bulk-images-restored" style="text-align:center;padding:20px 40px;border:1px solid #e2e8f0;border-radius:12px;background:#f8fafc;">';
                $output .= '<div style="margin-bottom:8px;"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#22b73a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg></div>';
                $output .= '<h3 style="font-size:28px;font-weight:700;margin:0 0 4px;">' . $restoredCount . '</h3>';
                $output .= '<h5 style="margin:0;color:#64748b;font-size:13px;">Images Restored</h5>';
                $output .= '</div>';
                $output .= '</div>';
            }
        }

        $output .= '</div>';
        $output .= '</div>';
        $output .= '</div>';

        delete_option('wps_ic_parsed_images');
        delete_option('wps_ic_BulkStatus');
        delete_option('wps_ic_bulk_process');
        set_transient('wps_ic_bulk_done', true, 60);

        wp_send_json_success(['html' => $output]);
    }

    /**
     * @return void
     * @since v6
     */
    public function wps_ic_isBulkRunning()
    {
        // Default
        $output = 'not-running';

        // Check the option
        $bulkRunning = get_option('wps_ic_bulk_process');
        if ($bulkRunning) {
            if (!empty($bulkRunning['status'])) {
                if ($bulkRunning['status'] == 'compressing') {
                    $output = 'compressing';
                } else {
                    $output = 'restoring';
                }

                wp_send_json_success($output);
            }
        }

        wp_send_json_error($output);
    }

    /**
     * @return void
     * @since v6
     */
    public function wpc_ic_start_bulk_restore()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }
        // Performance Lab - generate webp on upload
        if (function_exists('webp_uploads_create_sources_property')) {
            wp_send_json_error(['msg' => 'performance-lab-compatibility']);
        }

        // Delete previously parsed images
        delete_transient('wps_ic_bulk_done');
        delete_option('wps_ic_parsed_images');

        $local = new wps_ic_local();
        $imagesToRestore = $local->prepareRestoreImages();

        $olderBackup = false;
        if (!empty($imagesToRestore)) {
            foreach ($imagesToRestore['compressed'] as $imageID => $image) {
                $olderBackup = $this->olderBackup($imageID);
            }

            if ($olderBackup) {
                delete_option('wps_ic_parsed_images');
                delete_option('wps_ic_BulkStatus');
                delete_option('wps_ic_bulk_process');
                set_transient('wps_ic_bulk_done', true, 60);
                wp_send_json_success('older-backup');
            }
        }

        $send = $local->sendToAPI('restore');

        if ($send['status'] == 'success') {
            update_option('wps_ic_bulk_process', ['date' => date('y-m-d H:i:s'), 'status' => 'restoring']);
            set_transient('wps_ic_bulk_running', date('y-m-d H:i:s'), 60 * 5);

            // Send restore call
            $local = new wps_ic_local();

            // Send the call to API
            $send = $local->sendBulkRestoreToApi();

            if ($send['status'] == 'failed') {

                $reason = $send['reason'];

                if ($reason == 'bad-apikey') {
                    $reason = 'bulk-process-bad-apikey';
                }

                wp_send_json_error(['msg' => $reason, 'send' => print_r($send, true)]);

            } elseif ($send['status'] == 'success') {

                update_option('wps_ic_bulk_process', ['date' => date('y-m-d H:i:s'), 'status' => 'restoring']);
                set_transient('wps_ic_bulk_running', date('y-m-d H:i:s'), 60 * 5);
                wp_send_json_success($send);
            } else {
                wp_send_json_error($send);
            }


            wp_send_json_success($send);
        } else {
            wp_send_json_error($send);
        }
    }

    public function olderBackup($imageID)
    {
        return false;
        $backup_images = get_post_meta($imageID, 'ic_backup_images', true);

        if (!empty($backup_images) && is_array($backup_images)) {
            $compressed_images = get_post_meta($imageID, 'ic_compressed_images', true);

            // Remove Generated Images
            if (!empty($compressed_images)) {

                foreach ($compressed_images as $index => $path) {
                    if (strpos($index, 'webp') !== false) {
                        if (file_exists($path)) {
                            unlink($path);
                        }
                    }
                }

            }

            $upload_dir = wp_get_upload_dir();
            $sizes = get_intermediate_image_sizes();
            foreach ($sizes as $i => $size) {
                clearstatcache();
                $image = image_get_intermediate_size($imageID, $size);
                if ($image['path']) {
                    $path = $upload_dir['basedir'] . '/' . $image['path'];
                    if (file_exists($path)) {
                        unlink($path);
                    }
                }
            }

            $path_to_image = get_attached_file($imageID);

            // Restore only full
            $restore_image_path = $backup_images['full'];

            // If backup file exists
            if (file_exists($restore_image_path)) {
                unlink($path_to_image);

                // Restore from local backups
                $copy = copy($restore_image_path, $path_to_image);

                // Delete the backup
                unlink($restore_image_path);
            }

            clearstatcache();

            wp_update_attachment_metadata($imageID, wp_generate_attachment_metadata($imageID, $path_to_image));

            delete_transient('wps_ic_compress_' . $imageID);
            delete_post_meta($imageID, 'ic_bulk_running');

            // Remove meta tags
            delete_post_meta($imageID, 'ic_stats');
            delete_post_meta($imageID, 'ic_compressed_images');
            delete_post_meta($imageID, 'ic_compressed_thumbs');
            delete_post_meta($imageID, 'ic_backup_images');
            update_post_meta($imageID, 'ic_status', 'restored');

            return true;
        }

        return false;
    }

    /**
     * @return void
     * @since v6
     */
    public function wpc_ic_start_bulk_compress()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        // Performance Lab - generate webp on upload
        if (function_exists('webp_uploads_create_sources_property')) {
            wp_send_json_error(['msg' => 'performance-lab-compatibility']);
        }

        // Raise the memory and time limit
        ini_set('memory_limit', '2024M');
        ini_set('max_execution_time', '180');

        // Delete previously parsed images
        delete_transient('wps_ic_bulk_done');
        delete_option('wps_ic_parsed_images');
        delete_option('wps_ic_bulk_counter');

        $local = new wps_ic_local();

        // Send the call to API
        $send = $local->sendBulkToApi();

        if ($send['status'] == 'failed') {

            $reason = $send['reason'];

            if ($reason == 'bad-apikey') {
                $reason = 'bulk-process-bad-apikey';
            }

            wp_send_json_error(['msg' => $reason, 'send' => print_r($send, true)]);

        } elseif ($send['status'] == 'success') {
            update_option('wps_ic_bulk_process', ['date' => date('y-m-d H:i:s'), 'status' => 'compressing']);
            set_transient('wps_ic_bulk_running', date('y-m-d H:i:s'), 60 * 5);
            wp_send_json_success($send);
        } else {
            wp_send_json_error($send);
        }
    }

    public function wps_ic_remove_cname()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $cname_class = new wps_ic_cname();
        $cname_class->remove();
    }

    public function wps_ic_cname_retry()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $cname_class = new wps_ic_cname();
        $cname_class->retry();
    }

    public function wps_ic_remove_key()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $cache = new wps_ic_cache_integrations();
        $cache->remove_key();

        wp_send_json_success();
    }

    public function wpc_ic_set_mode()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $options = new wps_ic_options();
        $preset = sanitize_text_field($_POST['value']);
        $configuration = $options->get_preset($preset);
        update_option(WPS_IC_SETTINGS, $configuration);
        wp_send_json_success($configuration);
    }

    public function wpc_ic_ajax_set_preset()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $options = new wps_ic_options();
        $preset = sanitize_text_field($_POST['value']);
        $configuration = $options->get_preset($preset);
        wp_send_json_success($configuration);
    }

    public function wps_ic_cname_add()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $cname_input = !empty($_POST['cname']) ? $_POST['cname'] : null;

        $cname_class = new wps_ic_cname();
        $cname_class->add($cname_input);
    }


    public function wps_ic_exclude_list()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $excludeList = $_POST['excludeList'];
        $lazyExcludeList = $_POST['lazyExcludeList'];
        $delayExcludeList = $_POST['delayExcludeList'];

        if (!empty($excludeList)) {
            $excludeList = rtrim($excludeList, "\n");
            $excludeList = explode("\n", $excludeList);
            update_option('wpc-ic-external-url-exclude', $excludeList);
        } else {
            delete_option('wpc-ic-external-url-exclude');
        }

        if (!empty($lazyExcludeList)) {
            $lazyExcludeList = rtrim($lazyExcludeList, "\n");
            $lazyExcludeList = explode("\n", $lazyExcludeList);
            update_option('wpc-ic-lazy-exclude', $lazyExcludeList);
        } else {
            delete_option('wpc-ic-lazy-exclude');
        }

        if (!empty($delayExcludeList)) {
            $delayExcludeList = rtrim($delayExcludeList, "\n");
            $delayExcludeList = explode("\n", $delayExcludeList);
            update_option('wpc-ic-delay-js-exclude', $delayExcludeList);
        } else {
            delete_option('wpc-ic-delay-js-exclude');
        }

        wp_send_json_success();
    }

    public function wps_ic_geolocation_force()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        global $wps_ic;

        $post = $_POST['location'];

        if ($post == 'Automatic') {
            $geolocation = $this->geoLocateAjax();
            wp_send_json_success($geolocation);
        }

        $location_data = ['server' => 'frankfurt.zapwp.net', 'continent' => 'EU', 'continent_name' => 'Europe', 'country' => 'DE', 'country_name' => 'Germany'];

        switch ($post) {
            case 'EU':
                break;
            case 'US':
                $location_data = ['server' => 'nyc.zapwp.net', 'continent' => 'US', 'continent_name' => 'United States', 'country' => 'US', 'country_name' => 'United States'];
                break;
            case 'OC':
                $location_data = ['server' => 'sydney.zapwp.net', 'continent' => 'OC', 'continent_name' => 'Oceania', 'country' => 'AU', 'country_name' => 'Australia'];
                break;
            case 'AS':
                $location_data = ['server' => 'singapore.zapwp.net', 'continent' => 'AS', 'continent_name' => 'Asia', 'country' => 'Singapore', 'country_name' => 'Singapore'];
                break;
        }

        update_option('wpc-ic-force-location', $location_data);
        update_option('wps_ic_geo_locate_v2', $location_data);

        wp_send_json_success($location_data);
    }

    public function wps_ic_geolocation()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        global $wps_ic;
        $geolocation = $this->geoLocateAjax();
        wp_send_json_success($geolocation);
    }

    public function wps_ic_RestoreFinished()
    {
        global $wps_ic;

        $count = $_POST['count'] . ' of ' . $_POST['count'];

        $output = '<div class="wps-ic-bulk-html-wrapper">';
        $output .= '<div class="bulk-restore-container">';

        $output .= '<div class="bulk-restore-preview-container">';
        $output .= '<div class="bulk-restore-preview-inner">';
        $output .= '<div class="bulk-restore-preview-image-holder">';
        $output .= '<img src="' . WPS_IC_URI . 'assets/images/bulk/restore-completed-image_opt.png' . '">';
        $output .= '</div>';
        $output .= '</div>';
        $output .= '</div>';

        $output .= '<div class="bulk-restore-info">';

        $output .= '<div class="bulk-restore-status-top-left">';
        $output .= '<img src="' . WPS_IC_URI . 'assets/images/shield.svg' . '">';
        $output .= '<span class="badge">';
        $output .= '<i class="icon-check"></i> Restored';
        $output .= '</span>';
        $output .= '</div>';

        $output .= '<div class="bulk-restore-status-top-right">';
        $output .= '<h3>' . $count . '</h3>';
        $output .= '<h5>Images Restored</h5>';
        $output .= '</div>';

        $output .= '<div class="bulk-restore-status-container">';
        $output .= '<h4>Image Restore Complete!</h4>';
        $output .= '<span>We have successfully restored all of your images.</span>';
        $output .= '<div class="bulk-status-progress-bar">
              <div class="progress-bar-outer">
                <div class="progress-bar-inner" style="width: 100%;"></div>
              </div>
            </div>';
        $output .= '</div>';

        $output .= '</div>';

        $output .= '</div>';

        wp_send_json_success(['html' => $output]);
    }

    public function wps_ic_doBulkRestore()
    {
        global $wps_ic;

        $lastProgress = $_POST['lastProgress'];
        $bulkStats = get_transient('wps_ic_bulk_stats');
        $compressed_images_queue = get_transient('wps_ic_restore_queue');

        if (empty($bulkStats['images_restored'])) {
            $bulkStats['images_restored'] = 0;
        }

        if ($compressed_images_queue['queue']) {
            $attID = $compressed_images_queue['queue'][0];

            // First Image
            set_transient('wps_ic_restore_' . $attID, ['imageID' => $attID, 'status' => 'restoring'], 0);

            // do the restore
            self::$local->restore($attID);

            set_transient('wps_ic_restore_' . $attID, ['imageID' => $attID, 'status' => 'restored'], 0);

            unset($compressed_images_queue['queue'][0]);
            $compressed_images_queue['queue'] = array_values($compressed_images_queue['queue']);

            // Sleep so that it takes longer
            sleep(2);

            /**
             * Calculate Progress
             */
            $leftover_images = count($compressed_images_queue['queue']);
            $total_images = $compressed_images_queue['total_images'];
            $done_images = $total_images - $leftover_images;
            $progress_percent = round(($done_images / $total_images) * 100);

            // Bulk Stats
            $bulkStats['images_restored'] += 1;

            set_transient('wps_ic_bulk_stats', $bulkStats, 1800);
            set_transient('wps_ic_restore_queue', $compressed_images_queue, 1800);

            wp_send_json_success(['done' => $attID, 'progress' => $progress_percent, 'finished' => $done_images, 'leftover' => $leftover_images, 'total' => $total_images, 'todo' => $compressed_images_queue, 'html' => $this->bulkRestoreHtml($attID, $lastProgress)]);
        }

        wp_send_json_error();
    }

    public function wps_ic_media_library_bulk_heartbeat()
    {
        global $wpdb, $wps_ic;
        $like_compress = $wpdb->esc_like('_transient_wps_ic_compress_') . '%';
        $like_restore  = $wpdb->esc_like('_transient_wps_ic_restore_') . '%';

        $heartbeat_query = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT *
         FROM {$wpdb->options}
         WHERE option_name LIKE %s
            OR option_name LIKE %s",
                $like_compress,
                $like_restore
            )
        );

        $html = [];
        if ($heartbeat_query) {
            foreach ($heartbeat_query as $heartbeat_item) {
                $value = unserialize(untrailingslashit($heartbeat_item->option_value));

                if ($value['status'] == 'compressed' || $value['status'] == 'restored') {
                    $html[$value['imageID']] = $wps_ic->media_library->compress_details($value['imageID']);
                    delete_transient('wps_ic_compress_' . $value['imageID']);
                    delete_transient('wps_ic_restore_' . $value['imageID']);
                }
            }

            wp_send_json_success($html);
        }

        wp_send_json_error();
    }

    /**
     * Live Compress
     */
    public function wps_ic_restore_live()
    {
        // Performance Lab - generate webp on upload
        if (function_exists('webp_uploads_create_sources_property')) {
            wp_send_json_error(['msg' => 'performance-lab-compatibility']);
        }


        // do the restore
        self::$local->restoreV4($_POST['attachment_id']);

        sleep(1);
        wp_send_json_success();
    }

    public function wps_ic_compress_live()
    {
        // Performance Lab - generate webp on upload
        if (function_exists('webp_uploads_create_sources_property')) {
            wp_send_json_error(['msg' => 'performance-lab-compatibility']);
        }

        self::$accountStatus = parent::getAccountStatusMemory();

        $stats = get_post_meta($_POST['attachment_id'], 'ic_status', true);
        if (!empty($stats) && $stats == 'compressed') {
            wp_send_json_error(['msg' => 'file-already-compressed']);
        }


        self::$local->singleCompressV4($_POST['attachment_id']);

        sleep(1);
        wp_send_json_success();
    }

    /**
     * Count Uncompressed Images
     */
    public function wps_ic_count_uncompressed_images()
    {
        global $wpdb;

        $args = ['post_type' => 'attachment', 'post_status' => 'inherit', 'posts_per_page' => -1, 'meta_query' => ['relation' => 'AND', ['key' => 'wps_ic_data', 'compare' => 'NOT EXISTS'], ['key' => 'wps_ic_exclude', 'compare' => 'NOT EXISTS']]];

        $uncompressed_attachments = new WP_Query($args);
        $total_file_size = 0;
        if ($uncompressed_attachments->have_posts()) {
            while ($uncompressed_attachments->have_posts()) {
                $uncompressed_attachments->the_post();
                $postID = get_the_ID();

                $filesize = filesize(get_attached_file($postID));
                $total_file_size += $filesize;
            }
        }

        wp_send_json_success(['uncompressed' => $total_file_size, 'unit' => 'Bytes']);
    }

    public function wps_ic_save_mode()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wpc_save_mode')) {
            wp_send_json_error('Forbidden.');
        }
        $preset = sanitize_text_field($_POST['mode']);
        $cdn = sanitize_text_field($_POST['cdn']);
        $options = new wps_ic_options();
        $settings = $options->get_preset($preset);


        if ($cdn == 'true') {
            $settings['live-cdn'] = '1';
            $settings['serve'] = ['jpg' => '1', 'png' => '1', 'gif' => '1', 'svg' => '1', 'fonts' => '1'];
            $settings['css'] = 1;
            $settings['js'] = 1;
            $settings['fonts'] = 1;
            $settings['generate_adaptive'] = 1;
            $settings['generate_webp'] = 1;
            $settings['retina'] = 1;
        } else {
            $settings['live-cdn'] = '0';
            $settings['serve'] = ['jpg' => '0', 'png' => '0', 'gif' => '0', 'svg' => '0', 'fonts' => '0'];
            $settings['css'] = 0;
            $settings['js'] = 0;
            $settings['fonts'] = 0;
            $settings['generate_adaptive'] = 0;
            $settings['generate_webp'] = 0;
            $settings['retina'] = 0;
        }


        $wpc_excludes = get_option('wpc-inline');
        $wpc_excludes['inline_js'] = explode(',', "jquery.min,adaptive,jquery-migrate,wp-includes");
        update_option('wpc-inline', $wpc_excludes);

        $wpc_excludes = get_option('wpc-excludes');
        $wpc_excludes['delay_js'] = [];
        update_option('wpc-excludes', $wpc_excludes);


        update_option(WPS_IC_SETTINGS, $settings);
        update_option(WPS_IC_PRESET, $preset);

        // Preload Page
        $cacheLogic = new wps_ic_cache();

        // Remove generateCriticalCSS Options
        delete_option('wps_ic_gen_hp_url');

        if (!class_exists('wps_ic_htaccess')) {
            include_once WPS_IC_DIR . 'classes/htaccess.class.php';
        }

        $htaccess = new wps_ic_htaccess();

        if ($preset == 'safe') {
            $htaccess->removeHtaccessRules();
            $htaccess->removeAdvancedCache();
            $htaccess->setWPCache(false);
        } else {
            // Setup Advanced Caching
            // Add WP_CACHE to wp-config.php
            $htaccess->setWPCache(true);
            $htaccess->setAdvancedCache();
        }

        $cache = new wps_ic_cache_integrations();
        $cache::purgeAll();

        if (!empty($_POST['activation']) && $_POST['activation']) {
            $warmup_class = new wps_ic_preload_warmup();
            $warmup_class->optimizeSingle('home');
        }

        wp_send_json_success();
    }

    public function wps_ic_get_per_page_settings_html()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $id = sanitize_text_field($_POST['id']);

        $wpc_excludes = get_option('wpc-excludes', []);
        $settings = isset($wpc_excludes['per_page_settings'][$id]) ? $wpc_excludes['per_page_settings'][$id] : [];

        if (isset($settings['skip_lazy'])) {
            $skip_lazy = $settings['skip_lazy'];
        } else {
            $skip_lazy = '';
        }

        if (isset($settings['purge_on_new_post'])) {
            $purge_on_new_post = 'checked';
        } else {
            $purge_on_new_post = '';
        }

        // Start building the HTML
        $html = '<div class="cdn-popup-loading" style="display: none;">';
        $html .= '<div class="wpc-popup-saving-logo-container">';
        $html .= '<div class="wpc-popup-saving-preparing-logo">';
        $html .= '<img src="' . WPS_IC_URI . 'assets/images/logo/blue-icon.svg" class="wpc-ic-popup-logo-saving"/>';
        $html .= '<img src="' . WPS_IC_URI . 'assets/preparing.svg" class="wpc-ic-popup-logo-saving-loader"/>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '<div class="cdn-popup-content">';
        $html .= '<div class="cdn-popup-top">';
        $html .= '<div class="inline-heading">';
        $html .= '<div class="inline-heading-icon">';
        $html .= '<img src="' . WPS_IC_URI . 'assets/images/icon-exclude-from-cdn.svg"/>';
        $html .= '</div>';
        $html .= '<div class="inline-heading-text">';
        $html .= '<h3>Per Page Settings</h3>';
        $html .= '<p>These settings will apply only to the current page.</p>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '<form method="post" class="wpc-save-popup-data" action="#">';
        $html .= '<div class="cdn-popup-content-full">';
        $html .= '<div class="cdn-popup-content-inner">';
        $html .= '<div class="wps-default-excludes-container">';

        $html .= '<div style="display:flex;align-items:baseline;">';
        $html .= '<strong>Skip Lazy Loading: &nbsp</strong>';
        $html .= '<p>Skip &nbsp</p> <input type="number" class="per_page_lazy_skip" min="0" max="99" value="' . $skip_lazy . '"/> <p>&nbsp Images</p>';
        $html .= '</div>';
        $html .= '</div>';

        $html .= '<div class="wps-default-excludes-container">';
        $html .= '<div class="wps-default-excludes-enabled-checkbox-container" style="padding-left: 0">';
        $html .= '<input type="checkbox" class="wps-default-excludes-enabled-checkbox wps-purge-on-new-post" ' . $purge_on_new_post . '>';
        $html .= '<p>Purge cache on new post</p>';
        $html .= '</div>';


        $html .= '</div>';
        $html .= '<div class="wps-empty-row">&nbsp;</div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '<a href="#" class="btn btn-primary btn-active btn-save btn-exclude-pages-save">' . esc_html__('Save', WPS_IC_TEXTDOMAIN) . '</a>';
        $html .= '</form>';
        $html .= '</div>';


        // Return the HTML as an AJAX response
        wp_send_json_success(['html' => $html]);
    }

    public function wps_ic_save_per_page_settings()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        if (empty($_POST['id'])) {
            wp_send_json_error('Forbidden.');
        }

        $id = sanitize_text_field($_POST['id']);
        $skip_lazy = false;
        $purge_on_new_post = false;

        if (isset($_POST['skip_lazy'])) {
            $skip_lazy = sanitize_text_field($_POST['skip_lazy']);
        }

        if (isset($_POST['purge_on_new_post'])) {
            $purge_on_new_post = sanitize_text_field($_POST['purge_on_new_post']);
        }

        $wpc_excludes = get_option('wpc-excludes', []);

        if (!isset($wpc_excludes['per_page_settings'])) {
            $wpc_excludes['per_page_settings'] = [];
        }

        if (empty($wpc_excludes['per_page_settings'][$id])) {
            $wpc_excludes['per_page_settings'][$id] = [];
        }

        if ($purge_on_new_post != 'false') {
            $wpc_excludes['per_page_settings'][$id]['purge_on_new_post'] = $skip_lazy;
        } else {
            unset($wpc_excludes['per_page_settings'][$id]['purge_on_new_post']);
        }

        if ($skip_lazy !== false) {
            $wpc_excludes['per_page_settings'][$id]['skip_lazy'] = $skip_lazy;
        } else {
            unset($wpc_excludes['per_page_settings'][$id]['skip_lazy']);
        }

        // Update the 'wpc-excludes' option with the new data
        update_option('wpc-excludes', $wpc_excludes);

        if ($id == 'home') {
            $url = site_url();
        } else {
            $url = get_permalink($id);
        }
        $keys = new wps_ic_url_key();
        $url_key = $keys->setup($url);

        $cache = new wps_ic_cache_integrations();
        $cache::purgeAll($url_key);


        wp_send_json_success($url_key);

    }

    public function wps_ic_get_page_excludes_popup_html()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $id = sanitize_text_field($_POST['id']);
        $setting = sanitize_text_field($_POST['setting']);

        // Fetch the data from 'wpc-excludes' option
        $wpc_excludes = get_option('wpc-excludes', []);
        $excludes = isset($wpc_excludes['page_excludes_files'][$id]) ? $wpc_excludes['page_excludes_files'][$id] : [];

        if (!empty($excludes[$setting])) {
            $current_excludes = implode("\n", $excludes[$setting]);
        } else {
            $current_excludes = '';
        }

        $setting_name = ['cdn' => esc_html__('CDN', WPS_IC_TEXTDOMAIN), 'adaptive' => esc_html__('Adaptive Images', WPS_IC_TEXTDOMAIN), 'advanced_cache' => esc_html__('Advanced Cache', WPS_IC_TEXTDOMAIN), 'critical_css' => esc_html__('Critical CSS', WPS_IC_TEXTDOMAIN), 'delay_js' => esc_html__('JavaScript', WPS_IC_TEXTDOMAIN), 'delay_js_v2' => esc_html__('JavaScript', WPS_IC_TEXTDOMAIN)];

        // Start building the HTML
        $html = '<div class="cdn-popup-loading" style="display: none;">';
        $html .= '<div class="wpc-popup-saving-logo-container">';
        $html .= '<div class="wpc-popup-saving-preparing-logo">';
        $html .= '<img src="' . WPS_IC_URI . 'assets/images/logo/blue-icon.svg" class="wpc-ic-popup-logo-saving"/>';
        $html .= '<img src="' . WPS_IC_URI . 'assets/preparing.svg" class="wpc-ic-popup-logo-saving-loader"/>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '<div class="cdn-popup-content">';
        $html .= '<div class="cdn-popup-top">';
        $html .= '<div class="inline-heading">';
        $html .= '<div class="inline-heading-icon">';
        $html .= '<img src="' . WPS_IC_URI . 'assets/images/icon-exclude-from-cdn.svg"/>';
        $html .= '</div>';
        $html .= '<div class="inline-heading-text">';
        $html .= '<h3>' . sprintf(esc_html__('Exclude from %s', WPS_IC_TEXTDOMAIN), $setting_name[$setting]) . '</h3>';
        $html .= '<p>' . esc_html__('List files or paths to exclude. Partial names work too — we match automatically.', WPS_IC_TEXTDOMAIN) . '</p>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '<form method="post" class="wpc-save-popup-data" action="#">';
        $html .= '<div class="cdn-popup-content-full">';
        $html .= '<div class="cdn-popup-content-inner">';
        $html .= '<textarea name="exclude-pages" data-setting-name="' . $setting . '" data-page-id="' . $id . '" class="exclude-list-textarea-value" placeholder="' . esc_attr__('e.g. plugin-name/js/script.js, scripts.js, anyimage.jpg', WPS_IC_TEXTDOMAIN) . '">';
        $html .= $current_excludes;
        $html .= '</textarea>';
        $html .= '<div class="wps-empty-row">&nbsp;</div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '<a href="#" class="btn btn-primary btn-active btn-save btn-exclude-pages-save">' . esc_html__('Save', WPS_IC_TEXTDOMAIN) . '</a>';
        $html .= '<div class="wps-example-section">';
        $html .= '<button type="button" class="wps-example-toggle-btn">' . esc_html__('See Examples', WPS_IC_TEXTDOMAIN) . ' <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg></button>';
        $html .= '<div class="wps-example-list" style="display:none;">';
        $html .= '<div><div>';
        $html .= '<p>' . esc_html__('.svg would exclude all assets with that extension', WPS_IC_TEXTDOMAIN) . '</p>';
        $html .= '<p>' . esc_html__('imagename would exclude any file with that name', WPS_IC_TEXTDOMAIN) . '</p>';
        $html .= '<p>' . esc_html__('/myplugin/image.jpg would exclude that specific file', WPS_IC_TEXTDOMAIN) . '</p>';
        $html .= '<p>' . esc_html__('/wp-content/myplugin/ would exclude everything using that path', WPS_IC_TEXTDOMAIN) . '</p>';
        $html .= '</div></div>';
        $html .= '</div>';
        $html .= '</div>';
        $html .= '</form>';
        $html .= '</div>';


        // Return the HTML as an AJAX response
        wp_send_json_success(['html' => $html]);
    }

    public function wps_ic_save_page_excludes_popup()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        if (empty($_POST['id']) || empty($_POST['setting'])) {
            wp_send_json_error('Forbidden.');
        }

        $id = sanitize_text_field($_POST['id']);
        $setting = sanitize_text_field($_POST['setting']);
        $excludes = $_POST['excludes'];
        $excludes = rtrim($excludes, "\n");
        $excludes = explode("\n", $excludes);

        // Fetch the entire 'wpc-excludes' option
        $wpc_excludes = get_option('wpc-excludes', []);

        // Create 'page_excludes_files' key if it doesn't exist
        if (!isset($wpc_excludes['page_excludes_files'])) {
            $wpc_excludes['page_excludes_files'] = [];
        }

        if (empty($wpc_excludes['page_excludes_files'][$id])) {
            $wpc_excludes['page_excludes_files'][$id] = [];
        }

        $wpc_excludes['page_excludes_files'][$id][$setting] = $excludes;

        // Update the 'wpc-excludes' option with the new data
        update_option('wpc-excludes', $wpc_excludes);

        if ($id == 'home') {
            $url = site_url();
        } else {
            $url = get_permalink($id);
        }
        $keys = new wps_ic_url_key();
        $url_key = $keys->setup(get_permalink($url));

        $cache = new wps_ic_cache_integrations();
        $cache::purgeAll($url_key);

        if ($setting == 'combine_js' || $setting == 'css_combine' || $setting == 'delay_js') {
            $cache::purgeCombinedFiles($url_key);
        }

        if ($setting == 'critical_css') {
            $cache::purgeCriticalFiles($url_key);
        }

        wp_send_json_success();
    }

    public function wps_ic_get_optimization_status_pages()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        if (isset($_POST['post_type']) && is_array($_POST['post_type'])) {
            $post_type = array_map('sanitize_text_field', $_POST['post_type']);
        } else {
            $post_type = ['page', 'post', 'product'];
        }

        $search = '';
        if (!empty($_POST['search'])) {
            $search = sanitize_text_field($_POST['search']);
        }

        $page = isset($_POST['page']) ? $_POST['page'] : 1;
        $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
        $limit = 10;

        $process_all = false;
        if (isset($_POST['post_status']) && is_array($_POST['post_status'])) {
            $post_status = array_map('sanitize_text_field', $_POST['post_status']);
            //To get statuses, we have to process all posts
            $process_all = true;
        } else {
            $post_status = ['optimized', 'skipped', 'unoptimized'];
        }

        $cf = get_option(WPS_IC_CF);
        $cfLive = false;
        if ($cf && isset($cf['settings'])) {
            $cfLive = ($cf['settings']['assets'] == '1' && $cf['settings']['cdn'] == '0');
        }
        $allowLive = get_option('wps_ic_allow_live') && !$cfLive;
        if ($allowLive) {
            $allowLive = '1';
        }

        $warmup_class = new wps_ic_preload_warmup();
        if ($process_all) {
            $pages = $warmup_class->getPagesForFiltering($post_type, $post_status, $page, $offset, $search);
            $response = ['pages' => $pages['pages'], 'total_pages' => ceil($pages['total'] / 10), 'global_settings' => self::$settings, 'allow_live' => $allowLive];
        } else {
            $pages = $warmup_class->getOptimizationsStatus($post_type, $page, $offset, $limit, $search);

            wp_reset_postdata();
            $args = ['post_type' => $post_type, 'limit' => $limit, 'fields' => 'ids', 'post_status' => 'publish', 's' => $search];

            $query = new WP_Query($args);

            $response = ['pages' => $pages, 'total_pages' => $query->max_num_pages, 'global_settings' => self::$settings, 'allow_live' => $allowLive];
        }


        $locked = [];
        $locked['cdn'] = false;
        $locked['advanced_cache'] = false;
        $locked['adaptive'] = false;
        $locked['critical_css'] = false;
        $locked['delay_js'] = false;

        $response['locked'] = $locked;

        wp_send_json_success($response);
    }

    public function wps_ic_save_optimization_status()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $id = sanitize_text_field($_POST['id']);
        $setting_name = sanitize_text_field($_POST['setting_name']);
        $setting_action = sanitize_text_field($_POST['setting_action']);
        $changed = false;

        if ($setting_action == 'purge') {
            $keys = new wps_ic_url_key();
            if ($id == 'home') {
                $url_key = $keys->setup(home_url());
            } else {
                $url_key = $keys->setup(get_permalink($id));
            }

            $cache = new wps_ic_cache_integrations();

            if ($setting_name == 'combine_js' || $setting_name == 'css_combine' || $setting_name == 'delay_js') {
                $cache::purgeCombinedFiles($url_key);
            }
            if ($setting_name == 'critical_css') {
                $cache::purgeCriticalFiles($url_key);
            }

            $cache::purgeAll($url_key);
        } else if ($setting_action == 'generate' && $setting_name == 'critical_css') {
            $critical = new wps_criticalCss();
            $critical->generateCriticalCSS($id, true);
        } else {

            $wpc_excludes = get_option('wpc-excludes', []);

            // If 'page_excludes' doesn't exist within 'wpc-excludes', initialize it as an empty array
            if (!isset($wpc_excludes['page_excludes'])) {
                $wpc_excludes['page_excludes'] = [];
            }


            // Ensure each $post_id is an array within 'page_excludes'
            if (!isset($wpc_excludes['page_excludes'][$id])) {
                $wpc_excludes['page_excludes'][$id] = [];
            }

            $current_value = isset($wpc_excludes['page_excludes'][$id][$setting_name]) ? $wpc_excludes['page_excludes'][$id][$setting_name] : null;
            if ($setting_action == 'force_on') {
                if ($current_value !== '1') {
                    $wpc_excludes['page_excludes'][$id][$setting_name] = '1';
                    $changed = true;
                }
            } elseif ($setting_action == 'force_off') {
                if ($current_value !== '0') {
                    $wpc_excludes['page_excludes'][$id][$setting_name] = '0';
                    $changed = true;
                }
            } elseif ($setting_action === 'global') {
                if ($current_value !== null) {
                    unset($wpc_excludes['page_excludes'][$id][$setting_name]);
                    $changed = true;
                }
            }


            if ($changed) {

                $keys = new wps_ic_url_key();
                if ($id == 'home') {
                    $url_key = $keys->setup(home_url());
                } else {
                    $url_key = $keys->setup(get_permalink($id));
                }

                $cache = new wps_ic_cache_integrations();

                if ($setting_name == 'combine_js' || $setting_name == 'css_combine' || $setting_name == 'delay_js') {
                    $cache::purgeCombinedFiles($url_key);
                }
                if ($setting_name == 'critical_css') {
                    $cache::purgeCriticalFiles($url_key);
                }

                $cache::purgeAll($url_key);

                // Update the 'wpc-excludes' option with the modified data
                update_option('wpc-excludes', $wpc_excludes);
            }
        }

        wp_send_json_success();
    }


    public function wpsRunQuickTest()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        if (empty(self::$options['api_key'])) {
            wp_send_json_error('not-connected');
        }

        if (get_transient('wpc_test_running')) {
            wp_send_json_error('already-running');
        }

        $id = sanitize_text_field($_POST['id']);
        $dash = true;

        set_transient('wpc_test_running', 'running', 5 * 60);

        $warmup_class = new wps_ic_preload_warmup();
        $warmup_class->optimizeSingle('home', true, $dash);
        wp_send_json_error('error');
    }


    public function wps_ic_run_single_optimization()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        if (empty(self::$options['api_key'])) {
            wp_send_json_error('not-connected');
        }

        $id = sanitize_text_field($_POST['id']);
        if (!empty($_POST['dash'])) {
            $dash = sanitize_text_field($_POST['dash']);
        } else {
            $dash = false;
        }


        $warmup_class = new wps_ic_preload_warmup();
        $warmup_class->optimizeSingle($id, true, $dash);
        wp_send_json_error('error');
    }


    public function wps_ic_resetTest()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $url = home_url();
        $url_key_class = new wps_ic_url_key();
        $url_key = $url_key_class->setup($url);

        // Only purge HTML cache for the tested page — preserves CDN cache,
        // Critical CSS, and CSS/JS hashes so visitors aren't affected
        $cache = new wps_ic_cache_integrations();
        $cache::purgeCacheFiles($url_key);

        $requests = new wps_ic_requests();

        $tests = get_option(WPS_IC_TESTS);
        unset($tests['home']);
        update_option(WPS_IC_TESTS, $tests);

        // Save history of tests
        $history = get_option(WPS_IC_LITE_GPS_HISTORY);
        if (empty($history)) {
            $history = [];
        }
        $history[time()] = get_option(WPS_IC_LITE_GPS);
        update_option(WPS_IC_LITE_GPS_HISTORY, $history);

        // Delete data
        delete_transient('wpc_test_running');
        delete_transient('wpc_initial_test');
        delete_option(WPS_IC_LITE_GPS);
        delete_option(WPC_WARMUP_LOG_SETTING);

        set_transient('wpc_initial_test', 'running', 5 * 60);

        // Test
        $args = ['url' => home_url(), 'version' => self::$version, 'plugin_version' => self::$version, 'hash' => time() . mt_rand(100, 9999), 'apikey' => get_option(WPS_IC_OPTIONS)['api_key']];
        $response = $requests->POST(self::$PAGESPEED_URL_HOME, $args, ['timeout' => 5, 'blocking' => true, 'headers' => array('Content-Type' => 'application/json')]);

        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);

        if (isset($data['jobId'])) {
            $job_id = $data['jobId'];
            set_transient(WPS_IC_JOB_TRANSIENT, $job_id, 60 * 10);
            wp_send_json_success('started');
        } else {
            set_transient(WPS_IC_JOB_TRANSIENT, 'failed', 60 * 10);
        }

        wp_send_json_error();
    }


    public function wps_ic_save_cache_cookies_settings()
    {

        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $cookies_setting = get_option('wps_ic_cache_cookies', []);

        $cache_cookies = sanitize_textarea_field($_POST['cache_cookies']);
        $cache_cookies = rtrim($cache_cookies, "\n");
        $cache_cookies = explode("\n", $cache_cookies);
        $cookies_setting['cookies'] = $cache_cookies;

        $exclude_cookies = sanitize_textarea_field($_POST['exclude_cookies']);
        $exclude_cookies = rtrim($exclude_cookies, "\n");
        $exclude_cookies = explode("\n", $exclude_cookies);
        $cookies_setting['exclude_cookies'] = $exclude_cookies;

        $updated = update_option('wps_ic_cache_cookies', $cookies_setting);

        if ($updated) {
            $cache = new wps_ic_cache_integrations();
            $cache::purgeAll(false, false, false, false);

            $settings = get_option(WPS_IC_SETTINGS);
            if (!empty($settings['cache']['advanced']) && $settings['cache']['advanced'] == '1') {
                if (!class_exists('wps_ic_htaccess')) {
                    include_once WPS_IC_DIR . 'classes/htaccess.class.php';
                }

                $htaccess = new wps_ic_htaccess();
                $htaccess->setAdvancedCache();
            }
        }

        wp_send_json_success();
    }

    public function wps_ic_get_cache_cookies()
    {
        $cookies_setting = get_option('wps_ic_cache_cookies');

        if ($cookies_setting === false) {
            $options = new wps_ic_options();
            $cookies_setting = $options->get_preset('cache_cookies');
            update_option('wps_ic_cache_cookies', $cookies_setting);
        }

        if (!empty($cookies_setting['cookies'])) {
            $cache_cookies = implode("\n", $cookies_setting['cookies']);

        }

        if (!empty($cookies_setting['exclude_cookies'])) {
            $exclude_cookies = implode("\n", $cookies_setting['exclude_cookies']);
        }

        wp_send_json_success(['cache_cookies' => $cache_cookies ?? '', 'exclude_cookies' => $exclude_cookies ?? '']);
    }


    public function wps_ic_run_tests()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        die();

        $id = sanitize_text_field($_POST['id']);
        $retest = sanitize_text_field($_POST['retest']);

        $warmup_class = new wps_ic_preload_warmup();
        if ($warmup_class->isOptimized($id) == '1') {
            $warmup_class->doTest($id, $retest, true);
            #$warmup_class->doTestLCP($id, true);
        } else {
            $warmup_class->optimizeSingle($id);
        }
    }

    public function wps_ic_check_optimization_status()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        if (empty(self::$options['api_key'])) {
            wp_send_json_error('not-connected');
        }

        if (isset($_POST['optimize']) && is_array($_POST['optimize'])) {
            $optimize = array_map('sanitize_text_field', $_POST['optimize']);
            update_option('wpc-warmup-selector', $optimize);
        } elseif (isset($_POST['optimize']) && $_POST['optimize'] == 'false') {
            //we are not on our settings page
        } elseif (isset($_POST['optimize']) && $_POST['optimize'] == 'do-not-optimize') {
            update_option('wpc-warmup-selector', 'do-not-optimize');
        } else {
            delete_option('wpc-warmup-selector');
        }

        $warmup_class = new wps_ic_preload_warmup();
        $pages = $warmup_class->getPagesToOptimize();

        $status = $warmup_class->get_optimization_status();
        //local addition
        if (!empty($status['mode']) && $status['mode'] == 'local') {
            $next_page = reset($pages['pages']);
            if ($next_page !== false) {
                $transient = get_transient('wpc_last_optimised_page');
                if (!empty($transient)) {
                    if ($transient['id'] == $next_page['id'] && $transient['count'] == 2) {
                        $warmup_class->addError($next_page['id'], 'skip');
                    } else if ($transient['id'] == $next_page['id'] && $transient['count'] == 1) {
                        $count = 2;
                    } else {
                        $count = 1;
                    }
                }
                if ($warmup_class->isRedirected($next_page['link'])) {
                    $warmup_class->addError($next_page['id'], 'redirect');
                }
                $warmup_class->localCacheWarmup($next_page['link']);
                $status['id'] = $next_page['id'];
                $status['pageTitle'] = ($status['id'] === 'home') ? 'Home Page' : get_the_title($status['id']);
                $status['status'] = 'warmup';
                set_transient('wpc_last_optimised_page', ['id' => $next_page['id'], 'count' => $count]);
            }
        }
        //end local addition

        if ($pages['unoptimized'] == 0) {
            $check = get_transient('wpc-page-optimizations-status-check');
            if ($check === false) {
                //wait a minute maybe we are still testing
                $transient = get_transient('wpc-page-optimizations-status');
                set_transient('wpc-page-optimizations-status', $transient, 60);
                set_transient('wpc-page-optimizations-status-check', 'true', 62);
            }
        }

        $response = ['optimizationStatus' => $status, 'optimized' => $pages['total'] - $pages['unoptimized'], 'total' => $pages['total'], 'connectivity' => true, $next_page, $pages['pages']];
        wp_send_json_success($response);
    }

    public function wps_ic_start_optimizations()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        delete_option('wpc-warmup-errors');
        $warmup_class = new wps_ic_preload_warmup();
        $warmup_class->startOptimizations();
    }

    public function wps_ic_stop_optimizations()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $warmup_class = new wps_ic_preload_warmup();
        $warmup_class->stopOptimizations();
    }

    public function wps_ic_test_api_connectivity()
    {
        if (!current_user_can('manage_wpc_settings') || !wp_verify_nonce($_POST['nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $warmup = new wps_ic_preload_warmup();
        $results = $warmup->connectivityTest();

        wp_send_json_success($results);
    }

    public function wps_ic_save_purge_hooks_settings()
    {

        if ((!current_user_can('manage_wpc_settings') && !current_user_can('manage_wpc_purge')) || !wp_verify_nonce($_POST['wps_ic_nonce'], 'wps_ic_nonce_action')) {
            wp_send_json_error('Forbidden.');
        }

        $purge_rules = get_option('wps_ic_purge_rules', []);
        if (!isset($purge_rules['post_publish'])) {
            $purge_rules['post_publish'] = [];
        }

        $all_pages = sanitize_text_field($_POST['all_pages']);
        $home_page = sanitize_text_field($_POST['home_page']);
        $recent_posts_widget = sanitize_text_field($_POST['recent_posts_widget']);
        $archive_pages = sanitize_text_field($_POST['archive_pages']);
        $purge_rules['post-publish']['all-pages'] = $all_pages;
        $purge_rules['post-publish']['home-page'] = $home_page;
        $purge_rules['post-publish']['recent-posts-widget'] = $recent_posts_widget;
        $purge_rules['post-publish']['archive-pages'] = $archive_pages;

        $hooks = sanitize_textarea_field($_POST['hooks']);
        $hooks = rtrim($hooks, "\n");
        $hooks = explode("\n", $hooks);
        $purge_rules['hooks'] = $hooks;

        $scheduled = sanitize_text_field($_POST['scheduled']);
        $purge_rules['scheduled'] = $scheduled;

        $updated = update_option('wps_ic_purge_rules', $purge_rules);

        if ($updated) {
            $cache = new wps_ic_cache_integrations();
            $cache::purgeAll(false, false, false, false);
        }

        wp_send_json_success();
    }

    public function wps_ic_get_purge_rules()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $purge_rules = get_option('wps_ic_purge_rules');

        if (empty($purge_rules)) {
            $options = new wps_ic_options();
            $purge_rules = $options->get_preset('purge_rules');
            update_option('wps_ic_purge_rules', $purge_rules);
        }

        $post_publish = $purge_rules['post-publish'];

        //Checkboxes for post publish purge
        $all_pages = 0;
        $home_page = 0;
        $recent_posts_widget = 0;
        $archive_pages = 0;
        if (!empty($post_publish['all-pages']) && $post_publish['all-pages'] == '1') {
            $all_pages = 1;
        }
        if (!empty($post_publish['home-page']) && $post_publish['home-page'] == '1') {
            $home_page = 1;
        }
        if (!empty($post_publish['recent-posts-widget']) && $post_publish['recent-posts-widget'] == '1') {
            $recent_posts_widget = 1;
        }
        if (!empty($post_publish['archive-pages']) && $post_publish['archive-pages'] == '1') {
            $archive_pages = 1;
        }


        if (empty($purge_rules['hooks'])) {
            $hooks = '';
        } else {
            $hooks = implode("\n", $purge_rules['hooks']);
        }

        $scheduled = '';
        if (!empty($purge_rules['scheduled'])) {
            $scheduled = $purge_rules['scheduled'];
        }

        wp_send_json_success(['hooks' => $hooks, 'all_pages' => $all_pages, 'home_page' => $home_page, 'recent_posts_widget' => $recent_posts_widget, 'archive_pages' => $archive_pages, 'scheduled' => $scheduled]);
    }

    public function wps_ic_export_settings()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $settings       = sanitize_text_field($_POST['settings'] ?? '');
        $excludes       = sanitize_text_field($_POST['excludes'] ?? '');
        $cache          = sanitize_text_field($_POST['cache'] ?? '');
        $cache_cookies  = sanitize_text_field($_POST['cookies'] ?? '');

        $json = [];

        if (!empty($settings)) {
            $json['settings'] = get_option(WPS_IC_SETTINGS);
        }

        if (!empty($excludes)) {
            $json['excludes'] = get_option('wpc-excludes', []);
        }

        if (!empty($cache)) {
            $json['cache'] = get_option('wps_ic_purge_rules', []);

            // Don't export lists of archive pages
            unset($json['cache']['type-lists']);
        }

        if (!empty($cache_cookies)) {
            $json['cache_cookies'] = get_option('wps_ic_cache_cookies', []);
        }

        wp_send_json_success($json);
    }



    public function wps_ic_import_settings()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        // Get data
        $import_data = $_POST['importData'];

        if (empty($import_data)) {
            wp_send_json_error(['msg' => 'No import data provided']);
        }

        if (is_string($import_data)) {
            $decoded_data = json_decode($import_data, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                wp_send_json_error(['message' => 'JSON decode error: ' . json_last_error_msg()]);
            }

            $import_data = $decoded_data;
        }

        if (empty($import_data)) {
            wp_send_json_error(['msg' => 'No import data provided']);
        }

        $options_class = new wps_ic_options();

        if (isset($import_data['settings'])) {
            $import_data['settings'] = $options_class->setMissingSettings($import_data['settings']);
            update_option(WPS_IC_SETTINGS, $import_data['settings']);
        }

        if (isset($import_data['excludes'])) {
            update_option('wpc-excludes', $import_data['excludes']);
        }

        if (isset($import_data['cache'])) {
            update_option('wps_ic_purge_rules', $import_data['cache']);
        }

        // Import cache cookies
        if (isset($import_data['cache_cookies'])) {
            update_option('wps_ic_cache_cookies', $import_data['cache_cookies']);
        }

        $cache = new wps_ic_cache_integrations();
        $cache::purgeCriticalFiles();
        $cache::purgeAll();

        wp_send_json_success(['msg' => 'Settings imported successfully']);
    }


    public function wps_ic_set_default_settings()
    {
        // Verify nonce for security
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $options = new wps_ic_options();
        $purge_rules = $options->get_preset('purge_rules');
        update_option('wps_ic_purge_rules', $purge_rules);

        $configuration = $options->get_preset('aggressive');
        update_option(WPS_IC_SETTINGS, $configuration);
        update_option(WPS_IC_PRESET, 'aggressive');

        delete_option('wpc-excludes');

        $cache = new wps_ic_cache_integrations();
        $cache::purgeCriticalFiles();
        $cache::purgeAll(false, false, false, false);

        wp_send_json_success();
    }

    public function wps_ic_save_cf_cdn()
    {
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $cf = get_option(WPS_IC_CF);
        if ($cf === false) {
            wp_send_json_error(['message' => 'Cloudflare not connected'], 403);
            wp_die();
        }

        $cname = sanitize_text_field($_POST['cname']);
        if (empty($cname)) {
            wp_send_json_error('Empty CNAME');
        }

        $requests = new wps_ic_requests();

        $siteUrl = site_url();
        $zoneName = str_replace(array('http://', 'https://', '/'), '', $siteUrl);

        $options = get_option(WPS_IC_OPTIONS);
        $apikey = $options['api_key'];

        $body = $requests->GET(WPS_IC_KEYSURL, ['action' => 'updateCFCname', 'apikey' => $apikey, 'cname' => $cname, 'token' => $cf['token'], 'zoneName' => $zoneName, 'siteUrl' => site_url(), 'zone' => $cf['zone'], 'time' => microtime(true)], ['timeout' => 120]);

        if (!empty($body)) {
            $data = (array)$body->data;
            $cfCname = $data['cfName'];
            update_option(WPS_IC_CF_CNAME, $cfCname);
        } else {
            wp_send_json_error();
        }

        $cache = new wps_ic_cache_integrations();
        $cache->purgeAll(false, false, false, false);
        wp_send_json_success($body);
    }

    public function wps_ic_get_cf_cdn()
    {
        if (!isset($_POST['wps_ic_nonce']) || !check_ajax_referer('wps_ic_nonce_action', 'wps_ic_nonce', false)) {
            wp_send_json_error(['message' => 'Invalid nonce'], 403);
            wp_die();
        }

        // Check user capabilities
        if (!current_user_can('manage_wpc_settings')) {
            wp_send_json_error(['message' => 'Permission denied'], 403);
            wp_die();
        }

        $cfsdk = new WPC_CloudflareAPI();
        $cname = $cfsdk->getCfCname();

        wp_send_json_success(['cname' => $cname]);
    }

}