<?php
/**
 * Cache Invalidation Service
 *
 * Handles intelligent cache invalidation based on WooCommerce events,
 * product updates, stock changes, order status, and settings changes.
 *
 * @package Mamba\Modules\Caching\Services
 * @since   1.0.0
 */

namespace Mamba\Modules\Caching\Services;

use Mamba\Support\Logger;

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

/**
 * Class Invalidation
 *
 * Manages cache invalidation with smart clearing based on content relationships,
 * WooCommerce events, and settings changes. Supports HPOS detection for diagnostics.
 *
 * @since 1.0.0
 */
final class Invalidation {
    /**
     * Bootstrap invalidation hooks for settings changes
     */
    public static function bootstrap(): void {
        add_action('update_option_woocommerce_currency', [__CLASS__, 'onPricingOptionChanged'], 10, 3);
        add_action('update_option_woocommerce_tax_display_shop', [__CLASS__, 'onPricingOptionChanged'], 10, 3);
        
        // Additional pricing/display options that affect catalog HTML
        add_action('update_option_woocommerce_price_num_decimals', [__CLASS__, 'onPricingOptionChanged'], 10, 3);
        add_action('update_option_woocommerce_currency_pos', [__CLASS__, 'onPricingOptionChanged'], 10, 3);
        add_action('update_option_woocommerce_tax_round_at_subtotal', [__CLASS__, 'onPricingOptionChanged'], 10, 3);
        add_action('update_option_woocommerce_default_customer_address', [__CLASS__, 'onPricingOptionChanged'], 10, 3);
        
        // Media settings changes - trigger cache invalidation when features are enabled/disabled
        add_action('update_option_mamba_enable_webp_conversion', [__CLASS__, 'onWebPSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_avif_conversion', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_image_compression', [__CLASS__, 'onCompressionSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_lcp_optimization', [__CLASS__, 'onLCPSettingChanged'], 10, 3);
        add_action('update_option_mamba_add_image_dimensions', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        
        // Cache settings changes - trigger purge when cache behavior changes
        add_action('update_option_mamba_enable_page_cache', [__CLASS__, 'onPageCacheSettingChanged'], 10, 3);
        add_action('update_option_mamba_cache_ttl', [__CLASS__, 'onCacheTTLChanged'], 10, 3);
        add_action('update_option_mamba_include_accept_language_vary', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        
        // Overhead settings changes - trigger purge when features are enabled/disabled
        // These affect the HTML output (scripts, styles, content) so cache must be cleared
        
        // Content-affecting overhead settings
        add_action('update_option_mamba_simplify_price_html', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_disable_wc_assets_on_non_wc', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_hover_prefetch', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_disable_order_attribution', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        // HTML/CSS/JS minification settings
        add_action('update_option_mamba_enable_html_minify', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_html_minify_mode', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        // Also hook into add_option for first-time saves (update_option only fires on change)
        add_action('add_option_mamba_enable_html_minify', [__CLASS__, 'onMinifyEnabled'], 10, 2);
        add_action('add_option_mamba_html_minify_mode', [__CLASS__, 'onMinifyModeAdded'], 10, 2);
        
        // Font preloading and resource hints - affect page HTML
        add_action('update_option_mamba_enable_font_preload', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_preload_fonts', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_dns_prefetch', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_dns_prefetch_domains', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_preconnect', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_preconnect_domains', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        
        // Checkout optimization settings - affect checkout page HTML
        add_action('update_option_mamba_optimize_checkout_js', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_optimize_checkout_css', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_disable_checkout_fragments', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_lazy_load_payments', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_preconnect_payment_sdks', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        
        // Critical CSS settings - affect page HTML (inline styles)
        add_action('update_option_mamba_enable_critical_css', [__CLASS__, 'onCriticalCssSettingChanged'], 10, 3);
        add_action('update_option_mamba_enable_css_defer', [__CLASS__, 'onCriticalCssSettingChanged'], 10, 3);
        add_action('update_option_mamba_css_defer_all', [__CLASS__, 'onCriticalCssSettingChanged'], 10, 3);
        add_action('update_option_mamba_critical_css_force_include', [__CLASS__, 'onCriticalCssSettingChanged'], 10, 3);
        
        // JavaScript Delay settings - affect page HTML (script tags)
        add_action('update_option_mamba_enable_js_delay', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_js_delay_timeout', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_js_delay_scripts', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
        add_action('update_option_mamba_js_delay_exclusions', [__CLASS__, 'onContentAffectingSettingChanged'], 10, 3);
    }

    /**
     * Handle pricing option changes (currency, tax display)
     */
    public static function onPricingOptionChanged($old, $new, $option): void {
        if ($old === $new) return;
        self::clearStoreApi();
        \Mamba\Modules\Caching\Services\Preload\Preloader::schedule();
    }
    
    /**
     * Handle WebP conversion setting changes
     */
    public static function onWebPSettingChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // When WebP is disabled, clear all cache to remove WebP versions
        if ($old && !$new) {
            // WebP was disabled - clear all cache
            self::clearAll();
        } elseif (!$old && $new) {
            // WebP was enabled - clear cache to serve new WebP versions
            self::clearAll();
        }
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Mamba: WebP conversion setting changed from ' . ($old ? 'enabled' : 'disabled') . ' to ' . ($new ? 'enabled' : 'disabled'));
        }
    }
    
    /**
     * Handle image compression setting changes
     */
    public static function onCompressionSettingChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // When compression is disabled, clear all cache to remove compressed versions
        if ($old && !$new) {
            // Compression was disabled - clear all cache
            self::clearAll();
        } elseif (!$old && $new) {
            // Compression was enabled - clear cache to serve new compressed versions
            self::clearAll();
        }
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Mamba: Image compression setting changed from ' . ($old ? 'enabled' : 'disabled') . ' to ' . ($new ? 'enabled' : 'disabled'));
        }
    }
    
    /**
     * Handle LCP optimization setting changes
     */
    public static function onLCPSettingChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // LCP optimization affects page structure, so purge all pages
        self::clearAll();
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Mamba: LCP optimization setting changed from ' . ($old ? 'enabled' : 'disabled') . ' to ' . ($new ? 'enabled' : 'disabled'));
        }
    }
    
    /**
     * Handle content-affecting overhead setting changes
     */
    public static function onContentAffectingSettingChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // Content-affecting settings require cache invalidation
        self::clearAll();
        
        // Special handling: Reset minify savings when HTML minify is disabled
        if ($option === 'mamba_enable_html_minify' && !$new) {
            if (class_exists('Mamba\Support\SavingsTracker')) {
                \Mamba\Support\SavingsTracker::resetMinifySavings();
            }
        }
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $oldVal = is_bool($old) || is_numeric($old) ? ($old ? 'enabled' : 'disabled') : $old;
            $newVal = is_bool($new) || is_numeric($new) ? ($new ? 'enabled' : 'disabled') : $new;
            error_log('Mamba: Content-affecting setting changed: ' . $option . ' from ' . $oldVal . ' to ' . $newVal);
        }
    }
    
    /**
     * Handle Critical CSS setting changes
     * Clears both page cache and Critical CSS transient cache
     */
    public static function onCriticalCssSettingChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // Clear Critical CSS transient cache
        if (class_exists('Mamba\Modules\CriticalCss\Services\CriticalCssManager')) {
            $manager = new \Mamba\Modules\CriticalCss\Services\CriticalCssManager();
            $manager->clearCache();
        }
        
        // Clear page cache since HTML output changes
        self::clearAll();
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $oldVal = is_bool($old) || is_numeric($old) ? ($old ? 'enabled' : 'disabled') : $old;
            $newVal = is_bool($new) || is_numeric($new) ? ($new ? 'enabled' : 'disabled') : $new;
            error_log('Mamba: Critical CSS setting changed: ' . $option . ' from ' . $oldVal . ' to ' . $newVal);
        }
    }
    
    /**
     * Handle HTML minify being enabled for the first time
     * add_option hook has different signature: (option_name, value)
     */
    public static function onMinifyEnabled($option, $value): void {
        // Only trigger cache clear if minify is being enabled
        if ($value) {
            self::clearAll();
            
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Mamba: HTML minify enabled (first save) - clearing cache');
            }
        }
    }
    
    /**
     * Handle minify mode being added for the first time
     * add_option hook has different signature: (option_name, value)
     */
    public static function onMinifyModeAdded($option, $value): void {
        // Only trigger cache clear if aggressive mode is being set
        if ($value === 'aggressive') {
            self::clearAll();
            
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Mamba: Minify mode set to aggressive (first save) - clearing cache');
            }
        }
    }
    
    /**
     * Handle page cache setting changes
     */
    public static function onPageCacheSettingChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // Page cache setting changed - purge all cache
        self::clearAll();
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Mamba: Page cache setting changed from ' . ($old ? 'enabled' : 'disabled') . ' to ' . ($new ? 'enabled' : 'disabled'));
        }
    }
    
    /**
     * Handle cache TTL setting changes
     */
    public static function onCacheTTLChanged($old, $new, $option): void {
        if ($old === $new) return;
        
        // Cache TTL changed - purge all cache to apply new TTL
        self::clearAll();
        
        // Log the setting change
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Mamba: Cache TTL changed from ' . $old . ' to ' . $new);
        }
    }

    /**
     * Schedule a single event only once to prevent duplicate scheduling
     * Prevents cron storms during bulk operations
     */
    private static function scheduleOnce(string $hook, int $delay, array $args = [], string $lockKey = ''): void {
        $lockKey = $lockKey ?: 'mamba_sched_' . $hook;
        if (get_transient($lockKey)) return;
        set_transient($lockKey, 1, $delay + 30);
        if (!wp_next_scheduled($hook, $args)) {
            wp_schedule_single_event(time() + $delay, $hook, $args);
            
            // Spawn cron in background to ensure it runs on localhost/low-traffic sites
            // This triggers WP-Cron without waiting for a page visit
            if (function_exists('spawn_cron')) {
                spawn_cron();
            }
        }
    }

    /**
     * Detect role-based pricing plugins and add role dimensions to cache keys
     */
    public static function detectRoleBasedPricing(): array {
        $roleDimensions = [];
        
        // Common role-based pricing plugins
        $plugins = [
            'woocommerce-role-based-pricing' => 'Role-Based Pricing for WooCommerce',
            'woocommerce-dynamic-pricing' => 'WooCommerce Dynamic Pricing',
            'woocommerce-wholesale-prices' => 'WooCommerce Wholesale Prices',
            'woocommerce-wholesale-prices-premium' => 'WooCommerce Wholesale Prices Premium',
            'woocommerce-customer-specific-pricing' => 'Customer Specific Pricing',
            'woocommerce-memberships' => 'WooCommerce Memberships',
            'woocommerce-subscriptions' => 'WooCommerce Subscriptions',
        ];
        
        foreach ($plugins as $plugin => $name) {
            if (function_exists('is_plugin_active') && is_plugin_active($plugin . '/' . $plugin . '.php')) {
                $roleDimensions[] = 'plugin=' . sanitize_key($plugin);
                break; // Only need to detect one to add dimension
            }
        }
        
        // Allow custom detection
        return apply_filters('mamba_role_based_pricing_dimensions', $roleDimensions);
    }

    /**
     * Optional HPOS detection for diagnostics
     * Use: $is_hpos = \Mamba\Modules\Caching\Services\Invalidation::isHPOSEnabled();
     */
    public static function isHPOSEnabled(): bool {
        if (class_exists('\Automattic\WooCommerce\Utilities\Features')) {
            return \Automattic\WooCommerce\Utilities\Features::is_enabled('custom_order_tables');
        }
        return false;
    }
    public static function smartClear(int $postId): void {
        self::clearUrl(home_url('/'));
        $permalink = get_permalink($postId); if ($permalink) self::clearUrl($permalink);
        $type = get_post_type($postId);
        if ($type === 'product') self::clearRelatedProduct($postId);
        if ($type === 'post') { $blog = get_permalink(get_option('page_for_posts')); if ($blog) self::clearUrl($blog); }
    }
    public static function onProductUpdated(int $productId): void { 
        self::clearRelatedProduct($productId); 
        self::clearStoreApi();
    }
    public static function onStockChanged($productId): void { 
        self::clearRelatedProduct((int)$productId); 
        self::clearStoreApi();
    }
    public static function onProductPropsUpdated($product, $updatedProps): void {
        $affects = ['stock_quantity','stock_status','price','regular_price','sale_price','catalog_visibility'];
        if (array_intersect($affects, array_keys($updatedProps))) {
            self::clearRelatedProduct($product->get_id());
            self::clearStoreApi();
        }
        // Schedule sale boundary purges/warmups when sale dates change
        self::scheduleSaleEventsForProduct($product->get_id());
    }
    public static function onOrderCreated($orderId): void { 
        $shopUrl = wc_get_page_permalink('shop');
        self::clearUrl($shopUrl);
        
        // Schedule async warmup (non-blocking) to prevent timeouts
        if (get_option('mamba_enable_page_cache', 0)) {
            self::scheduleOnce('mamba_cache_preload', 5);
        }
    }
    public static function onOrderStatusChanged($orderId, $old, $new): void {
        if (in_array($new, ['cancelled','refunded']) || in_array($old, ['cancelled','refunded'])) {
            $shopUrl = wc_get_page_permalink('shop');
            self::clearUrl($shopUrl);
            
            // Schedule async warmup (non-blocking) to prevent timeouts
            if (get_option('mamba_enable_page_cache', 0)) {
                self::scheduleOnce('mamba_cache_preload', 5);
            }
        }
    }
    
    /**
     * Handle term relationship changes (products added/removed from categories/tags)
     */
    public static function onTermRelationshipChanged($objectId, $terms, $ttIds, $taxonomy, $append, $oldTtIds): void {
        $urlsToWarm = [];
        
        // Only handle product taxonomies (categories, tags, and attributes)
        if (!in_array($taxonomy, ['product_cat', 'product_tag']) && strpos($taxonomy, 'pa_') !== 0) return;
        
        // Clear the product page cache
        if ($objectId > 0) {
            $permalink = get_permalink($objectId);
            if ($permalink) {
                self::clearUrl($permalink);
                $urlsToWarm[] = $permalink;
            }
        }
        
        // Clear category/tag/attribute archive pages
        if (!empty($ttIds) && is_array($ttIds)) {
            foreach ($ttIds as $ttId) {
                $term = get_term_by('term_taxonomy_id', $ttId, $taxonomy);
                if ($term && !is_wp_error($term)) {
                    $url = get_term_link($term);
                    if (!is_wp_error($url)) {
                        self::clearUrl($url);
                        $urlsToWarm[] = $url;
                    }
                    // Bump appropriate tag type
                    if ($taxonomy === 'product_cat') {
                        Tags::bump('category', (string)$term->term_id);
                    } elseif ($taxonomy === 'product_tag') {
                        Tags::bump('tag', (string)$term->term_id);
                    } elseif (strpos($taxonomy, 'pa_') === 0) {
                        Tags::bump('attribute', (string)$term->term_id);
                    }
                }
            }
        }
        
        // Also clear old terms if they were removed
        if (!empty($oldTtIds) && is_array($oldTtIds)) {
            foreach ($oldTtIds as $ttId) {
                $term = get_term_by('term_taxonomy_id', $ttId, $taxonomy);
                if ($term && !is_wp_error($term)) {
                    $url = get_term_link($term);
                    if (!is_wp_error($url)) {
                        self::clearUrl($url);
                        $urlsToWarm[] = $url;
                    }
                    // Bump appropriate tag type
                    if ($taxonomy === 'product_cat') {
                        Tags::bump('category', (string)$term->term_id);
                    } elseif ($taxonomy === 'product_tag') {
                        Tags::bump('tag', (string)$term->term_id);
                    } elseif (strpos($taxonomy, 'pa_') === 0) {
                        Tags::bump('attribute', (string)$term->term_id);
                    }
                }
            }
        }
        
        // Always clear shop and homepage for product category/tag changes
        // This ensures product listing changes are reflected immediately
        if ($objectId > 0) {
            $shopUrl = wc_get_page_permalink('shop');
            $homeUrl = home_url('/');
            self::clearUrl($shopUrl);
            self::clearUrl($homeUrl);
            $urlsToWarm[] = $shopUrl;
            $urlsToWarm[] = $homeUrl;
        }
        
        // Clear Store API cache for term relationship changes
        self::clearStoreApi();
        
        // Schedule async warmup (non-blocking) to prevent timeouts during product save
        if (get_option('mamba_enable_page_cache', 0)) {
            self::scheduleOnce('mamba_cache_preload', 5);
        }
    }
    
    /**
     * Handle HPOS order stock changes
     */
    public static function onOrderStockChanged($orderId): void {
        // Get order items and clear cache for affected products
        if (function_exists('wc_get_order')) {
            $order = wc_get_order($orderId);
            if ($order) {
                foreach ($order->get_items() as $item) {
                    $productId = $item->get_product_id();
                    if ($productId > 0) {
                        self::clearRelatedProduct($productId);
                    }
                }
            }
        }
    }
    
    /**
     * Handle bulk stock operations (order fulfillment/cancellation)
     * Works in both HPOS and classic modes via WooCommerce CRUD layer
     */
    public static function onBulkStockOps($orderId): void {
        // Get order items and clear cache for affected products
        if (function_exists('wc_get_order')) {
            $order = wc_get_order($orderId);
            if ($order) {
                foreach ($order->get_items() as $item) {
                    $productId = $item->get_product_id();
                    if ($productId > 0) {
                        self::clearRelatedProduct($productId);
                        self::clearStoreApi();
                    }
                }
            }
        }
    }
    public static function onVariationSaved($variationId): void { wp_schedule_single_event(time()+10, 'mamba_deferred_wc_variation_clear', ['variation_id'=>$variationId]); }
    public static function runDeferredVariationClear($variationId): void {
        $v = wc_get_product($variationId);
        if ($v) {
            self::clearRelatedProduct($v->get_parent_id());
            self::clearStoreApi();
            self::scheduleSaleEventsForProduct($v->get_parent_id());
        }
    }
    public static function onStockSet($product): void {
        self::clearRelatedProduct($product->get_id());
        self::clearStoreApi();
        // Removed clearAll() call for production safety - clearRelatedProduct() already handles all necessary targeted invalidation
        // including product page, category pages, shop/homepage (if featured/on-sale), and tag invalidation
    }
    public static function clearRelatedProduct(int $productId): void {
        $urlsToWarm = [];
        
        $link = get_permalink($productId); 
        if ($link) {
            self::clearUrl($link);
            $urlsToWarm[] = $link;
        }
        
        $cats = get_the_terms($productId, 'product_cat');
        if ($cats && !is_wp_error($cats)) {
            foreach ($cats as $c) { 
                $url = get_term_link($c); 
                if (!is_wp_error($url)) {
                    self::clearUrl($url);
                    $urlsToWarm[] = $url;
                    // Also clear common pretty pagination variants for category archives
                    $maxCatPages = (int) apply_filters('mamba_archive_clear_pages', 3);
                    $catBase = rtrim($url, '/');
                    for ($i = 1; $i <= $maxCatPages; $i++) {
                        // Pretty pagination
                        $pretty = $catBase . '/page/' . $i . '/';
                        self::clearUrl($pretty);
                        $urlsToWarm[] = $pretty;
                        // Query-style pagination (for completeness)
                        $q = add_query_arg('paged', (string)$i, $url);
                        self::clearUrl($q);
                        $urlsToWarm[] = $q;
                    }
                }
            }
        }
        
        // Always clear shop (with pagination) and homepage for any product update
        $shopBase = wc_get_page_permalink('shop');
        $homeUrl = home_url('/');
        
        // Clear base shop URL and comprehensive, but bounded, pagination + sort variants
        $shopUrls = [];
        $shopTrim = rtrim((string)$shopBase, '/');
        // Base
        $shopUrls[] = $shopBase;
        $shopUrls[] = $shopTrim . '/';
        // Pagination (both query and pretty forms)
        $maxShopPages = (int) apply_filters('mamba_shop_clear_pages', 3);
        for ($i = 1; $i <= $maxShopPages; $i++) {
            $shopUrls[] = add_query_arg('paged', (string)$i, $shopBase);
            $shopUrls[] = $shopTrim . '/page/' . $i . '/';
        }
        // Common orderby variants (base page only to avoid explosion)
        $orderbyVariants = (array) apply_filters('mamba_shop_clear_orderby', ['price','price-desc','date','popularity','rating','name']);
        foreach ($orderbyVariants as $ob) {
            $shopUrls[] = add_query_arg('orderby', $ob, $shopBase);
        }
        // Clear all computed shop variations
        foreach (array_values(array_unique(array_filter($shopUrls))) as $url) {
            self::clearUrl($url);
            $urlsToWarm[] = $url;
        }
        
        // Clear homepage
        self::clearUrl($homeUrl);
        $urlsToWarm[] = $homeUrl;
        
        // Bump tags for tag-staleness detection on serve
        // Category/tag bumps already existed; add a shop bump so all shop variants become stale immediately
        Tags::bump('shop', '1');
        if ($cats && !is_wp_error($cats)) foreach ($cats as $c) Tags::bump('category', (string)$c->term_id);
        $tags = get_the_terms($productId, 'product_tag');
        if ($tags && !is_wp_error($tags)) foreach ($tags as $t) Tags::bump('tag', (string)$t->term_id);
        Tags::bump('product', (string)$productId);
        
        // Generate tags for CDN purging
        $cdnTags = [
            'product_' . $productId,
            'shop_page',  // Tag for shop page
            'product_archive'  // Generic product archive tag
        ];
        if ($cats && !is_wp_error($cats)) {
            foreach ($cats as $c) {
                $cdnTags[] = 'category_' . $c->term_id;
            }
        }
        if ($tags && !is_wp_error($tags)) {
            foreach ($tags as $t) {
                $cdnTags[] = 'tag_' . $t->term_id;
            }
        }
        $cdnTags[] = 'store_api_products';
        $cdnTags[] = 'store_api_catalog';
        
        // Trigger CDN tag purge
        do_action('mamba_purge_tags', $cdnTags);
        
        // Clear Store API cache for product-related changes
        self::clearStoreApi();
        
        // Schedule async warmup via cron (non-blocking) instead of synchronous HTTP requests
        // This prevents 504 timeouts during product save operations
        if (get_option('mamba_enable_page_cache', 0)) {
            self::scheduleOnce('mamba_cache_preload', 5);
        }
    }
    public static function clearAll(): void {
        // Log cache clear for debugging
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Mamba: clearAll() called - clearing entire cache');
        }
        
        // Cancel any running warmup jobs first to prevent stale cache from being created
        // This ensures settings changes take effect immediately
        if (class_exists('\Mamba\Modules\Caching\Services\Preload\BackgroundWarmup')) {
            \Mamba\Modules\Caching\Services\Preload\BackgroundWarmup::cancelAllJobs();
        }
        
        $dir = WP_CONTENT_DIR . '/cache/mamba';
        if (!is_dir($dir)) {
            Logger::info('Cache clear requested but cache directory does not exist - scheduling warmup anyway');
            // Still schedule warmup even if cache dir doesn't exist yet
            if (get_option('mamba_enable_page_cache', 0)) {
                self::scheduleOnce('mamba_cache_preload', 60);
            }
            return;
        }
        
        $filesDeleted = 0;
        $it = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($it as $fi) { 
            $todo = $fi->isDir() ? 'rmdir' : 'unlink'; 
            $result = $todo($fi->getRealPath());
            if ($result && !$fi->isDir()) {
                $filesDeleted++;
            }
            if (!$result && defined('WP_DEBUG') && WP_DEBUG) {
                error_log("Mamba: Failed to delete cache file: " . $fi->getRealPath());
            }
        }
        
        // Reset cache stats and recalculate size (should be 0 after clear)
        update_option('mamba_cache_stats', ['cache_hits'=>0,'cache_misses'=>0,'cache_size'=>0,'last_cleared'=>time()]);
        PageCache::recalculateCacheSize(); // Ensure accurate size tracking
        
        // FIX: Clear tag markers to prevent false stale detection after full cache clear
        if (class_exists('\Mamba\Modules\Caching\Services\TagMarkers')) {
            \Mamba\Modules\Caching\Services\TagMarkers::clear();
        }
        
        Logger::invalidation('full_cache', 'all', ['files_deleted' => $filesDeleted]);
        
        do_action('mamba_cache_purged');
        do_action('mamba_purge_all');
        
        // Schedule warmup after full cache clear (with delay to avoid cache stampede)
        if (get_option('mamba_enable_page_cache', 0)) {
            self::scheduleOnce('mamba_cache_preload', 60);
        }
    }
    public static function clearStoreApi(): void {
        // Clear Store API file cache
        if (class_exists('\Mamba\Modules\Caching\Services\StoreApiFileCache')) {
            \Mamba\Modules\Caching\Services\StoreApiFileCache::clear();
        }
        
        // Also clear legacy transients for backward compatibility
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_mamba_wc_store_api_%'));
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_timeout_mamba_wc_store_api_%'));
        
        Logger::invalidation('store_api', 'all');
        
        // Trigger CDN purge for Store API cache
        do_action('mamba_purge_tags', ['store_api_products', 'store_api_catalog', 'store_api_categories', 'store_api_tags']);
        
        // Trigger logging action
        do_action('mamba_store_api_cleared');
        
        // Schedule warmup after Store API cache clear (with delay to avoid cache stampede)
        if (get_option('mamba_enable_page_cache', 0)) {
            self::scheduleOnce('mamba_cache_preload', 30);
        }
    }
    public static function clearUrl(string $url): void {
        $desk = Paths::forUrl($url, false); $mob = Paths::forUrl($url, true);
        
        // Remove specific variants for device and track size reduction
        foreach ([$desk,$mob] as $p) { 
            if (!$p) continue; 
            foreach (['file','meta','lock'] as $k) { 
                if (isset($p[$k]) && file_exists($p[$k])) {
                    // Track cache size reduction before deletion
                    if ($k === 'file' && isset($p['meta'])) {
                        PageCache::removeCacheSize($p['file'], $p['meta']);
                    }
                    @unlink($p[$k]); 
                } 
            } 
        }
        
        // Additionally, remove all variant files under this URL directory to ensure country/lang/currency variants are purged together
        $baseDir = $desk['dir'] ?? ($mob['dir'] ?? '');
        if ($baseDir && is_dir($baseDir)) {
            // GLOB_BRACE is not available on all systems (e.g., Alpine Linux/musl)
            // Use multiple glob calls as fallback
            $variantFiles = [];
            if (defined('GLOB_BRACE')) {
                $variantFiles = glob($baseDir.'index-*.{html,meta.json,lock}', GLOB_BRACE) ?: [];
            } else {
                // Fallback for systems without GLOB_BRACE
                $variantFiles = array_merge(
                    glob($baseDir.'index-*.html') ?: [],
                    glob($baseDir.'index-*.meta.json') ?: [],
                    glob($baseDir.'index-*.lock') ?: []
                );
            }
            
            foreach ($variantFiles as $variantFile) {
                // Track cache size reduction for variant files
                if (strpos($variantFile, '.html') !== false) {
                    $metaFile = str_replace('.html', '.meta.json', $variantFile);
                    if (file_exists($metaFile)) {
                        PageCache::removeCacheSize($variantFile, $metaFile);
                    }
                }
                @unlink($variantFile);
            }
        }
        
        // Trigger CDN URL purge
        do_action('mamba_purge_urls', [$url]);
        
        // Note: Immediate warmup is now handled by calling methods to avoid conflicts
        // and ensure proper targeted warmup of all related URLs
    }
    // Schedule purges/warmups at sale boundaries
    public static function scheduleSaleEventsForProduct(int $productId): void {
        if ($productId <= 0) return;
        $product = wc_get_product($productId);
        if (!$product) return;
        
        // Clear previous hooks for this product (all types)
        wp_clear_scheduled_hook('mamba_product_sale_event', ['product_id'=>$productId, 'type'=>'start_pre']);
        wp_clear_scheduled_hook('mamba_product_sale_event', ['product_id'=>$productId, 'type'=>'start']);
        wp_clear_scheduled_hook('mamba_product_sale_event', ['product_id'=>$productId, 'type'=>'end']);
        wp_clear_scheduled_hook('mamba_product_sale_event', ['product_id'=>$productId, 'type'=>'end_safety']);
        
        // Get sale boundaries (with variation awareness for variable products)
        $from = null;
        $to = null;
        
        if ($product->is_type('variable')) {
            // For variable products, scan all children to find earliest start and latest end
            $children = $product->get_children();
            foreach ($children as $childId) {
                $child = wc_get_product($childId);
                if ($child && method_exists($child, 'get_date_on_sale_from') && method_exists($child, 'get_date_on_sale_to')) {
                    $childFrom = $child->get_date_on_sale_from();
                    $childTo = $child->get_date_on_sale_to();
                    
                    if ($childFrom instanceof \WC_DateTime) {
                        if ($from === null || $childFrom->getTimestamp() < $from->getTimestamp()) {
                            $from = $childFrom;
                        }
                    }
                    
                    if ($childTo instanceof \WC_DateTime) {
                        if ($to === null || $childTo->getTimestamp() > $to->getTimestamp()) {
                            $to = $childTo;
                        }
                    }
                }
            }
        } else {
            // For simple products, use direct sale dates
            $from = method_exists($product, 'get_date_on_sale_from') ? $product->get_date_on_sale_from() : null;
            $to = method_exists($product, 'get_date_on_sale_to') ? $product->get_date_on_sale_to() : null;
        }
        
        $now = time();
        
        // Schedule start events
        if ($from instanceof \WC_DateTime) {
            // Pre-purge: Clear old cache before sale starts (no warmup)
            $pre = max($now + 2, $from->getTimestamp() - 60);
            wp_schedule_single_event($pre, 'mamba_product_sale_event', [
                'product_id' => $productId, 
                'type' => 'start_pre'
            ]);
            
            // Main purge: After sale starts with new pricing (purge + warmup)
            $main = max($now + 2, $from->getTimestamp() + 1);
            wp_schedule_single_event($main, 'mamba_product_sale_event', [
                'product_id' => $productId, 
                'type' => 'start'
            ]);
        }
        
        // Schedule end events
        if ($to instanceof \WC_DateTime) {
            // Main purge: After sale ends (purge + warmup)
            $main = max($now + 2, $to->getTimestamp() + 1);
            wp_schedule_single_event($main, 'mamba_product_sale_event', [
                'product_id' => $productId, 
                'type' => 'end'
            ]);
            
            // Safety purge: Handle cron jitter (purge + warmup)
            $safety = $to->getTimestamp() + 30;
            wp_schedule_single_event($safety, 'mamba_product_sale_event', [
                'product_id' => $productId, 
                'type' => 'end_safety'
            ]);
        }
    }
    public static function handleSaleEvent($productId, $type): void {
        $pid = (int)$productId; 
        if ($pid <= 0) return;
        
        // Always purge cache and Store API
        self::clearRelatedProduct($pid);
        self::clearStoreApi();
        
        // Only warmup for non-pre events (start_pre should only purge, not warm)
        if ($type !== 'start_pre') {
            \Mamba\Modules\Caching\Services\Preload\Preloader::schedule();
        }
        
        // Log the event for debugging
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Mamba: Sale event processed - Product: {$pid}, Type: {$type}");
        }
    }
    public static function dailyCleanup(): void {
        $dir = WP_CONTENT_DIR . '/cache/mamba'; if (!is_dir($dir)) return;
        $week = time() - 7*DAY_IN_SECONDS; $it = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
        $cleaned = 0; foreach ($it as $fi) { if ($fi->isFile() && $fi->getMTime() < $week) { @unlink($fi->getRealPath()); $cleaned++; } }
        
        // Garbage collection for Store API file cache
        if (class_exists('\Mamba\Modules\Caching\Services\StoreApiFileCache')) {
            \Mamba\Modules\Caching\Services\StoreApiFileCache::gc();
        }
        
        // Note: Tag markers garbage collection is now handled automatically by CacheAdapter TTL
        
        // Garbage collection for object cache file fallback
        if (class_exists('\Mamba\Support\FileCache')) {
            \Mamba\Support\FileCache::gc();
        }
        
        // Recalculate cache size after cleanup to ensure accuracy
        if (class_exists('\Mamba\Modules\Caching\Services\PageCache')) {
            \Mamba\Modules\Caching\Services\PageCache::recalculateCacheSize();
        }
        
        update_option('mamba_cache_last_cleanup', ['time'=>time(),'files_cleaned'=>$cleaned]);
    }
    
    /**
     * Handle coupon changes - clear Store API and shop/homepage if coupons affect shop display
     */
    public static function onCouponChanged(): void {
        // Clear Store API cache as coupons can affect product pricing and availability
        self::clearStoreApi();
        
        // Clear shop and homepage as they may display active coupons/promotions
        $shopUrl = wc_get_page_permalink('shop');
        $homeUrl = home_url('/');
        self::clearUrl($shopUrl);
        self::clearUrl($homeUrl);
        
        // Schedule warmup to rebuild caches
        \Mamba\Modules\Caching\Services\Preload\Preloader::schedule();
        
        // Log the action for debugging
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Mamba: Coupon changed - Store API, shop, and homepage cache cleared");
        }
    }
    
    /**
     * Handle menu changes - clear shop/homepage if navigation contains dynamic product counts
     */
    public static function onMenuChanged(): void {
        // Clear shop and homepage as navigation menus may contain dynamic product counts
        // or category links that could be affected by menu changes
        $shopUrl = wc_get_page_permalink('shop');
        $homeUrl = home_url('/');
        self::clearUrl($shopUrl);
        self::clearUrl($homeUrl);
        
        // Schedule warmup to rebuild caches
        \Mamba\Modules\Caching\Services\Preload\Preloader::schedule();
        
        // Log the action for debugging
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log("Mamba: Menu changed - shop and homepage cache cleared");
        }
    }
    
    /**
     * Check for missed sale boundary events and trigger recovery
     * This provides a safety net for sites with low traffic or suspended WP-Cron
     */
    public static function checkMissedSaleEvents(): void {
        // Only run this check occasionally to avoid performance impact
        $lastCheck = get_option('mamba_last_sale_event_check', 0);
        if (time() - $lastCheck < 300) { // Check every 5 minutes max
            return;
        }
        
        update_option('mamba_last_sale_event_check', time());
        
        // Get products with active sale dates
        $args = [
            'post_type' => 'product',
            'post_status' => 'publish',
            'posts_per_page' => 50, // Process in batches
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
            'meta_query' => [
                'relation' => 'OR',
                [
                    'key' => '_sale_price_dates_from',
                    'value' => time() - 3600, // Sale started in last hour
                    'compare' => '>=',
                    'type' => 'NUMERIC'
                ],
                [
                    'key' => '_sale_price_dates_to',
                    'value' => time() - 3600, // Sale ended in last hour
                    'compare' => '>=',
                    'type' => 'NUMERIC'
                ]
            ]
        ];
        
        $products = get_posts($args);
        
        foreach ($products as $product) {
            $productObj = wc_get_product($product->ID);
            if (!$productObj) continue;
            
            // Check if we missed a sale boundary event
            $from = method_exists($productObj, 'get_date_on_sale_from') ? $productObj->get_date_on_sale_from() : null;
            $to = method_exists($productObj, 'get_date_on_sale_to') ? $productObj->get_date_on_sale_to() : null;
            
            $now = time();
            
            // Check for missed start event (sale started but no recent event)
            if ($from instanceof \WC_DateTime) {
                $startTime = $from->getTimestamp();
                if ($startTime <= $now && $startTime > $now - 3600) { // Started in last hour
                    // Check if we have a recent event for this product
                    $recentEvent = get_option('mamba_last_sale_event_' . $product->ID, 0);
                    if ($recentEvent < $startTime) {
                        // Missed event - trigger recovery
                        if (defined('WP_DEBUG') && WP_DEBUG) {
                            error_log("Mamba: Detected missed sale start event for product {$product->ID} - triggering recovery");
                        }
                        self::handleSaleEvent($product->ID, 'start');
                        update_option('mamba_last_sale_event_' . $product->ID, time());
                    }
                }
            }
            
            // Check for missed end event (sale ended but no recent event)
            if ($to instanceof \WC_DateTime) {
                $endTime = $to->getTimestamp();
                if ($endTime <= $now && $endTime > $now - 3600) { // Ended in last hour
                    // Check if we have a recent event for this product
                    $recentEvent = get_option('mamba_last_sale_event_' . $product->ID, 0);
                    if ($recentEvent < $endTime) {
                        // Missed event - trigger recovery
                        if (defined('WP_DEBUG') && WP_DEBUG) {
                            error_log("Mamba: Detected missed sale end event for product {$product->ID} - triggering recovery");
                        }
                        self::handleSaleEvent($product->ID, 'end');
                        update_option('mamba_last_sale_event_' . $product->ID, time());
                    }
                }
            }
        }
    }
}
