<?php
/**
 * Recommendation cache management – admin side.
 *
 * ============================================================================
 * WHAT THIS CLASS DOES
 * ============================================================================
 *
 * Provides store owners with one way to flush the recommendation cache
 * manually:
 *
 * 1. React admin editor – "Flush Cache Now" button inside the General tab
 *    of the store-testing page. Calls POST /preco/v1/cache/flush via the
 *    WordPress REST API and shows an inline success / error notice.
 *
 * ── AUTO-INVALIDATION HOOKS ─────────────────────────────────────────────────
 *
 * The cache is also flushed automatically on the following events so store
 * owners rarely need to flush manually:
 *
 *  • Product saved / updated  (save_post_product)
 *    – Guarantees that editing a product's category, price, or stock
 *      immediately invalidates any cached list that may include it.
 *
 *  • WooCommerce settings saved  (woocommerce_settings_saved)
 *    – Config changes in WooCommerce itself (tax, currency, etc.) can affect
 *      price-based recommendations.
 *
 *  • Plugin recommendation settings saved  (preco_settings_saved)
 *    – When a store owner updates the recommendation rules or sorting in this
 *      plugin's admin, the cache is wiped so the new configuration takes
 *      effect immediately without waiting for TTL expiry.
 *
 * ── SECURITY ────────────────────────────────────────────────────────────────
 *
 * The REST flush endpoint is protected by:
 *  - WordPress REST API nonce (X-WP-Nonce header set by @wordpress/api-fetch)
 *  - Capability check: manage_woocommerce (enforced in RecommendationsRest)
 *
 * @see \PRECO\Includes\Frontend\Core\RecommendationsCache::invalidate_all()
 *
 * @package PRECO
 */

namespace PRECO\Includes\Admin;

use PRECO\Includes\Frontend\Core\RecommendationsCache;

defined( 'ABSPATH' ) || exit;

/**
 * Recommendation cache management – admin side.
 */
class RecommendationsCacheAdmin {

	// =========================================================================
	// BOOTSTRAP
	// =========================================================================

	/**
	 * Register all WordPress hooks.
	 *
	 * Called once during plugin initialization.
	 *
	 * @return void
	 */
	public function register_hooks(): void {

		// ── Auto-invalidation: product saved / updated ────────────────────────
		// Priority 20 keeps this after WooCommerce's own save handlers.
		add_action( 'save_post_product', array( $this, 'on_product_saved' ), 20 );

		// ── Auto-invalidation: WooCommerce settings page saved ────────────────
		add_action( 'woocommerce_settings_saved', array( $this, 'on_wc_settings_saved' ) );

		// ── Auto-invalidation: this plugin's recommendation config saved ──────
		// Fire this action from REST handler / settings save wherever the
		// plugin stores its configuration. Example call in REST controller:
		// do_action( 'preco_settings_saved' );
		add_action( 'preco_settings_saved', array( $this, 'on_preco_settings_saved' ) );
	}

	// =========================================================================
	// AUTO-INVALIDATION CALLBACKS
	// =========================================================================

	/**
	 * Invalidate the cache when a product is created or updated.
	 *
	 * Uses bump_generation() instead of invalidate_all() intentionally:
	 *
	 *  - bump_generation() is a single UPDATE on one autoloaded option row.
	 *    Cost: O(1), ~1 ms, safe to run on every product save.
	 *
	 *  - invalidate_all() runs two LIKE DELETEs on wp_options which can scan
	 *    thousands of rows on a large store. Calling it on every product save
	 *    on a store with automated price/stock sync (e.g. 100 saves/min) would
	 *    continuously empty the cache, defeating the purpose entirely.
	 *
	 * The generation bump instantly makes all existing cache keys unreachable
	 * (they were hashed with the old generation). Old transient rows expire
	 * naturally via their TTL without any extra DB work.
	 *
	 * @param int $post_id The saved product post ID.
	 * @return void
	 */
	public function on_product_saved( int $post_id ): void {
		// Skip auto-saves and revisions – they do not represent real changes.
		if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
			return;
		}

		// Cheap O(1) invalidation: bump the generation counter.
		// New requests immediately start building fresh cache entries.
		RecommendationsCache::bump_generation();
	}

	/**
	 * Flush the cache when WooCommerce settings are saved.
	 *
	 * Tax changes, currency switches, or store address updates can affect
	 * price-based recommendation rules.
	 *
	 * @return void
	 */
	public function on_wc_settings_saved(): void {
		RecommendationsCache::invalidate_all();
	}

	/**
	 * Flush the cache when this plugin's recommendation configuration is saved.
	 *
	 * Fired via do_action( 'preco_settings_saved' ) from the REST save handler.
	 * Ensures rule / sorting changes take effect immediately without waiting
	 * for TTL expiry.
	 *
	 * @return void
	 */
	public function on_preco_settings_saved(): void {
		RecommendationsCache::invalidate_all();
	}
}
