<?php
/**
 * Cache Compatibility Layer for WAWP.
 *
 * Handles compatibility with popular caching plugins:
 * LiteSpeed Cache, WP Rocket, W3 Total Cache, WP Super Cache,
 * WP Fastest Cache, SG Optimizer, Breeze, NitroPack, FlyingPress, Hummingbird.
 *
 * @package WAWP
 */

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

/**
 * WAWP Cache Compatibility class.
 */
class WAWP_Cache_Compat {

	/**
	 * Initialize hooks — must be called on `plugins_loaded` priority 1.
	 */
	public static function init() {
		// --- 1. Exclude WAWP pages / REST / AJAX from ALL cache plugins ---
		add_filter( 'rocket_cache_reject_uri', array( __CLASS__, 'rocket_exclude_uris' ) );
		add_filter( 'rocket_cache_reject_cookies', array( __CLASS__, 'rocket_exclude_cookies' ) );
		add_filter( 'rocket_cache_dynamic_cookies', array( __CLASS__, 'rocket_dynamic_cookies' ) );
		add_filter( 'rocket_cdn_reject_files', array( __CLASS__, 'rocket_exclude_uris' ) );

		// LiteSpeed Cache.
		add_action( 'litespeed_init', array( __CLASS__, 'litespeed_nocache_on_wawp_pages' ) );
		add_filter( 'litespeed_optimize_js_excludes', array( __CLASS__, 'litespeed_exclude_wawp_js' ) );
		add_filter( 'litespeed_optm_cssjs_defer_exc', array( __CLASS__, 'litespeed_exclude_wawp_js' ) );

		// W3 Total Cache.
		add_filter( 'w3tc_pagecache_reject_reason', array( __CLASS__, 'w3tc_reject_wawp' ) );

		// WP Super Cache.
		add_filter( 'do_caching_request', array( __CLASS__, 'wpsc_reject_wawp' ) );

		// WP Fastest Cache.
		add_filter( 'wpsfc_clean_cache', '__return_false' ); // Prevent full purge on every request.

		// Breeze.
		add_filter( 'breeze_cache_exclude', array( __CLASS__, 'breeze_exclude_paths' ) );

		// Generic early nocache for REST / AJAX.
		add_action( 'rest_api_init', array( __CLASS__, 'set_nocache_headers_for_wawp_rest' ), 1 );
		add_action( 'admin_init', array( __CLASS__, 'set_nocache_on_wawp_admin' ), 1 );

		// --- 2. Nonce refresh endpoint (JS polls this to get a fresh nonce) ---
		add_action( 'wp_ajax_wawp_refresh_nonce', array( __CLASS__, 'ajax_refresh_nonce' ) );
		add_action( 'wp_ajax_nopriv_wawp_refresh_nonce', array( __CLASS__, 'ajax_refresh_nonce' ) );

		// --- 3. WP-Cron heartbeat: fire on every WAWP REST request (non-blocking) ---
		add_action( 'rest_api_init', array( __CLASS__, 'trigger_cron_on_wawp_rest' ), 99 );

		// --- 3b. Self-healing recurring Action Scheduler heartbeat ---
		add_action( 'init', array( __CLASS__, 'ensure_as_heartbeat_scheduled' ), 99 );
		add_action( 'wawp_as_cron_heartbeat', array( __CLASS__, 'as_heartbeat_tick' ) );

		// --- 4. Inject JS nonce refresher on frontend when cache plugins active ---
		add_action( 'wp_footer', array( __CLASS__, 'inject_nonce_refresher_script' ), 99 );
	}

	// =========================================================================
	// WP Rocket
	// =========================================================================

	/**
	 * Exclude WAWP admin page, REST, and AJAX from WP Rocket cache.
	 *
	 * @param array $uris Existing URIs.
	 * @return array
	 */
	public static function rocket_exclude_uris( $uris ) {
		if ( ! is_array( $uris ) ) {
			$uris = array();
		}
		$uris[] = '.*admin-ajax\.php';
		$uris[] = '.*wp-json/wawp/.*';
		$uris[] = '.*page=wawp.*';
		return $uris;
	}

	/**
	 * Exclude WAWP cookies from WP Rocket cache.
	 *
	 * @param array $cookies Existing cookies.
	 * @return array
	 */
	public static function rocket_exclude_cookies( $cookies ) {
		if ( ! is_array( $cookies ) ) {
			$cookies = array();
		}
		$cookies[] = 'wawp_session';
		$cookies[] = 'wawp_cart_tracked';
		return $cookies;
	}

	/**
	 * Add WAWP session cookie as a dynamic (per-user) cache cookie.
	 *
	 * @param array $cookies Existing dynamic cookies.
	 * @return array
	 */
	public static function rocket_dynamic_cookies( $cookies ) {
		if ( ! is_array( $cookies ) ) {
			$cookies = array();
		}
		$cookies[] = 'wawp_session';
		return $cookies;
	}

	// =========================================================================
	// LiteSpeed Cache
	// =========================================================================

	/**
	 * Tell LiteSpeed not to cache WAWP admin / REST pages.
	 */
	public static function litespeed_nocache_on_wawp_pages() {
		if ( ! class_exists( 'LiteSpeed\Core' ) && ! function_exists( 'litespeed_nobuffer_nofor' ) ) {
			return;
		}

		$page         = sanitize_key( (string) filter_input( INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS ) );
		$request_uri  = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		$is_wawp_page = (
			( is_admin() && 'wawp' === $page )
			|| ( defined( 'REST_REQUEST' ) && REST_REQUEST && strpos( $request_uri, '/wawp/' ) !== false )
			|| ( defined( 'DOING_AJAX' ) && DOING_AJAX )
		);

		if ( $is_wawp_page ) {
			if ( function_exists( 'do_action' ) ) {
				do_action( 'litespeed_control_set_nocache', 'WAWP admin/REST page' );
			}
			if ( ! defined( 'DONOTCACHEPAGE' ) ) {
				define( 'DONOTCACHEPAGE', true );
			}
		}
	}

	/**
	 * Exclude WAWP JS from LiteSpeed optimization to protect inline nonces.
	 *
	 * @param array $excludes Existing excludes.
	 * @return array
	 */
	public static function litespeed_exclude_wawp_js( $excludes ) {
		if ( ! is_array( $excludes ) ) {
			$excludes = array();
		}
		$excludes[] = 'wawp-app';
		$excludes[] = 'automation-web-platform';
		return $excludes;
	}

	// =========================================================================
	// W3 Total Cache
	// =========================================================================

	/**
	 * Reject WAWP REST / admin-ajax from W3TC page cache.
	 *
	 * @param bool $reject Current decision.
	 * @return bool
	 */
	public static function w3tc_reject_wawp( $reject ) {
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return true;
		}
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}
		return $reject;
	}

	// =========================================================================
	// WP Super Cache
	// =========================================================================

	/**
	 * Prevent WP Super Cache from caching WAWP AJAX / REST requests.
	 *
	 * @param bool $should_cache Whether to cache.
	 * @return bool
	 */
	public static function wpsc_reject_wawp( $should_cache ) {
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return false;
		}
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return false;
		}
		return $should_cache;
	}

	// =========================================================================
	// Breeze
	// =========================================================================

	/**
	 * Exclude WAWP paths from Breeze cache.
	 *
	 * @param array $paths Existing paths.
	 * @return array
	 */
	public static function breeze_exclude_paths( $paths ) {
		if ( ! is_array( $paths ) ) {
			$paths = array();
		}
		$paths[] = 'wp-json/wawp';
		$paths[] = 'admin-ajax.php';
		return $paths;
	}

	// =========================================================================
	// Generic REST / Admin nocache headers
	// =========================================================================

	/**
	 * Add no-cache headers for all WAWP REST API responses.
	 */
	public static function set_nocache_headers_for_wawp_rest() {
		$route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';
		if ( strpos( $route, '/wawp/' ) === 0 ) {
			nocache_headers();
			if ( ! defined( 'DONOTCACHEPAGE' ) ) {
				define( 'DONOTCACHEPAGE', true );
			}
			// LiteSpeed specific.
			if ( function_exists( 'do_action' ) ) {
				do_action( 'litespeed_control_set_nocache', 'WAWP REST API' );
			}
			// NitroPack.
			if ( ! defined( 'NITROPACK_CACHE_EXCLUDE' ) ) {
				define( 'NITROPACK_CACHE_EXCLUDE', true );
			}
		}
	}

	/**
	 * Mark WAWP admin page as no-cache.
	 */
	public static function set_nocache_on_wawp_admin() {
		$page = sanitize_key( (string) filter_input( INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS ) );
		if ( 'wawp' === $page ) {
			nocache_headers();
			if ( ! defined( 'DONOTCACHEPAGE' ) ) {
				define( 'DONOTCACHEPAGE', true );
			}
		}
	}

	// =========================================================================
	// Nonce refresh AJAX endpoint
	// =========================================================================

	/**
	 * Return a fresh wp_rest nonce via AJAX (no cache, called from JS).
	 */
	public static function ajax_refresh_nonce() {
		nocache_headers();
		wp_send_json_success( array( 'nonce' => wp_create_nonce( 'wp_rest' ) ) );
	}

	// =========================================================================
	// WP-Cron heartbeat — REST-triggered + Action Scheduler self-healing
	// =========================================================================

	/**
	 * Ping wp-cron.php non-blocking on every WAWP REST request.
	 * This means: every time a notification fires or the dashboard loads,
	 * a background cron tick is triggered — no visitor dependency needed.
	 */
	public static function trigger_cron_on_wawp_rest() {
		$route = $GLOBALS['wp']->query_vars['rest_route'] ?? ( $_SERVER['REQUEST_URI'] ?? '' );
		if ( strpos( $route, '/wawp/' ) === false ) {
			return;
		}

		if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
			return;
		}

		$last = (int) get_transient( 'wawp_cron_last_rest_ping' );
		if ( $last && ( time() - $last ) < 60 ) {
			return;
		}

		set_transient( 'wawp_cron_last_rest_ping', time(), 60 );

		$cron_url = add_query_arg( 'doing_wp_cron', sprintf( '%.22F', microtime( true ) ), site_url( 'wp-cron.php' ) );
		wp_remote_get(
			$cron_url,
			array(
				'timeout'   => 0.01,
				'blocking'  => false,
				'sslverify' => false,
				'headers'   => array( 'Cache-Control' => 'no-cache' ),
			)
		);
	}

	/**
	 * Ensure the Action Scheduler heartbeat is always scheduled.
	 * Action Scheduler survives caching plugins because it runs via its own
	 * async runner — completely independent of page visits.
	 */
	public static function ensure_as_heartbeat_scheduled() {
		if ( ! function_exists( 'as_next_scheduled_action' ) || ! function_exists( 'as_schedule_recurring_action' ) ) {
			return;
		}
		if ( ! class_exists( 'ActionScheduler' ) || ! ActionScheduler::is_initialized() ) {
			return;
		}
		if ( false === as_next_scheduled_action( 'wawp_as_cron_heartbeat' ) ) {
			as_schedule_recurring_action( time(), 60, 'wawp_as_cron_heartbeat', array(), 'wawp-cron' );
		}
	}

	/**
	 * Action Scheduler heartbeat tick — runs every 60 seconds via AS runner.
	 * Spawns a real WP-Cron execution so scheduled tasks never miss a beat.
	 */
	public static function as_heartbeat_tick() {
		if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
			return;
		}
		$cron_url = add_query_arg( 'doing_wp_cron', sprintf( '%.22F', microtime( true ) ), site_url( 'wp-cron.php' ) );
		wp_remote_get(
			$cron_url,
			array(
				'timeout'   => 0.01,
				'blocking'  => false,
				'sslverify' => false,
				'headers'   => array( 'Cache-Control' => 'no-cache' ),
			)
		);
	}

	// =========================================================================
	// Frontend nonce refresher (injected JS)
	// =========================================================================

	/**
	 * On frontend pages, inject a lightweight JS snippet that refreshes the
	 * wawp nonce every 10 minutes so cached pages don't break REST calls.
	 */
	public static function inject_nonce_refresher_script() {
		if ( is_admin() ) {
			return;
		}

		$active_cache = self::get_active_cache_plugin();
		if ( ! $active_cache ) {
			return;
		}

		$ajax_url = admin_url( 'admin-ajax.php' );
		?>
		<script id="wawp-nonce-refresher">
		(function() {
			var ajaxUrl = <?php echo wp_json_encode( $ajax_url ); ?>;
			function refreshWawpNonce() {
				var xhr = new XMLHttpRequest();
				xhr.open('POST', ajaxUrl, true);
				xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
				xhr.onload = function() {
					try {
						var data = JSON.parse(xhr.responseText);
						if (data && data.success && data.data && data.data.nonce) {
							if (window.wawpData && window.wawpData.global) {
								window.wawpData.global.wpRestNonce = data.data.nonce;
							}
						}
					} catch(e) {}
				};
				xhr.send('action=wawp_refresh_nonce');
			}
			// Refresh every 10 minutes.
			setInterval(refreshWawpNonce, 600000);
		})();
		</script>
		<?php
	}

	/**
	 * Detect which (if any) known cache plugin is active.
	 *
	 * @return string|false Plugin name or false.
	 */
	private static function get_active_cache_plugin() {
		$known = array(
			'wp-rocket/wp-rocket.php'                    => 'WP Rocket',
			'litespeed-cache/litespeed-cache.php'        => 'LiteSpeed Cache',
			'w3-total-cache/w3-total-cache.php'          => 'W3 Total Cache',
			'wp-super-cache/wp-cache.php'                => 'WP Super Cache',
			'wp-fastest-cache/wpFastestCache.php'        => 'WP Fastest Cache',
			'sg-cachepress/sg-cachepress.php'            => 'SG Optimizer',
			'breeze/breeze.php'                          => 'Breeze',
			'nitropack/nitropack.php'                    => 'NitroPack',
			'flying-press/flying-press.php'              => 'FlyingPress',
			'hummingbird-performance/wp-hummingbird.php' => 'Hummingbird',
			'wp-hummingbird/wp-hummingbird.php'          => 'Hummingbird',
			'autoptimize/autoptimize.php'                => 'Autoptimize',
			'swift-performance-lite/swift-performance-lite.php' => 'Swift Performance',
			'cache-enabler/cache-enabler.php'            => 'Cache Enabler',
		);
		foreach ( $known as $path => $name ) {
			if ( is_plugin_active( $path ) ) {
				return $name;
			}
		}
		return false;
	}

	// =========================================================================
	// Settings Save — purge all cache plugins
	// =========================================================================

	/**
	 * Purge the full page cache of every known cache plugin.
	 * Called after WAWP settings are saved via REST API.
	 */
	public static function purge_on_settings_save() {
		// LiteSpeed Cache.
		do_action( 'litespeed_purge_all' );

		// WP Rocket.
		if ( function_exists( 'rocket_clean_domain' ) ) {
			rocket_clean_domain();
		}

		// W3 Total Cache.
		if ( function_exists( 'w3tc_flush_all' ) ) {
			w3tc_flush_all();
		}

		// WP Super Cache.
		if ( function_exists( 'wp_cache_clear_cache' ) ) {
			wp_cache_clear_cache();
		}

		// WP Fastest Cache.
		if ( class_exists( 'WpFastestCache' ) && method_exists( 'WpFastestCache', 'deleteCache' ) ) {
			( new WpFastestCache() )->deleteCache( true );
		}

		// SG Optimizer.
		if ( function_exists( 'sg_cachepress_purge_cache' ) ) {
			sg_cachepress_purge_cache();
		}

		// Breeze.
		do_action( 'breeze_clear_all_cache' );

		// Hummingbird.
		do_action( 'wphb_clear_page_cache' );

		// NitroPack.
		if ( class_exists( 'NitroPack\\SDK\\NitroPack' ) ) {
			do_action( 'nitropack_integration_purge_all' );
		}

		// FlyingPress.
		do_action( 'flying_press_purge_all' );

		// Cache Enabler.
		do_action( 'cache_enabler_clear_complete_cache' );

		// Swift Performance.
		if ( class_exists( 'Swift_Performance_Cache' ) && method_exists( 'Swift_Performance_Cache', 'clear_all_cache' ) ) {
			Swift_Performance_Cache::clear_all_cache();
		}

		// WP core object cache.
		wp_cache_flush();
	}
}
