<?php
/**
 * Keeps smart-category membership live by re-evaluating a product whenever it
 * is created, updated (price/stock/attributes), trashed or deleted.
 *
 * @package SmartCategories
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class SCWC_Product_Hooks
 */
class SCWC_Product_Hooks {

	/**
	 * Guard against re-entrancy within a single request.
	 *
	 * @var array<int,bool>
	 */
	private static $in_progress = array();

	/**
	 * Wire product lifecycle hooks.
	 */
	public static function init() {
		add_action( 'woocommerce_update_product', array( __CLASS__, 'on_save' ), 20, 1 );
		add_action( 'woocommerce_new_product', array( __CLASS__, 'on_save' ), 20, 1 );

		// Stock changes that may not always bubble up as a full product update.
		add_action( 'woocommerce_product_set_stock', array( __CLASS__, 'on_stock_change' ), 20, 1 );
		add_action( 'woocommerce_variation_set_stock', array( __CLASS__, 'on_stock_change' ), 20, 1 );

		// Cleanup tracking rows only on PERMANENT deletion. Trashing is reversible
		// and leaves term relationships intact, so we must keep the tracking rows —
		// otherwise a restored product would still carry the term but be seen as a
		// "manual" (untracked) membership and never get re-evaluated.
		add_action( 'before_delete_post', array( __CLASS__, 'on_delete' ), 10, 1 );
		// Re-evaluate a product when it is restored from trash.
		add_action( 'untrashed_post', array( __CLASS__, 'on_untrash' ), 20, 1 );

		// Cleanup when a smart category term itself is deleted.
		add_action( 'pre_delete_term', array( __CLASS__, 'on_term_delete' ), 10, 2 );
	}

	/**
	 * Re-evaluate a saved product across all smart categories.
	 *
	 * @param int $product_id Product ID.
	 * @return void
	 */
	public static function on_save( $product_id ) {
		$product_id = (int) $product_id;
		if ( $product_id <= 0 || isset( self::$in_progress[ $product_id ] ) ) {
			return;
		}
		self::$in_progress[ $product_id ] = true;
		SCWC_Assigner::sync_product( $product_id );
		unset( self::$in_progress[ $product_id ] );
	}

	/**
	 * Handle stock-driven changes (resolves variations to their parent product).
	 *
	 * @param WC_Product $product Product or variation.
	 * @return void
	 */
	public static function on_stock_change( $product ) {
		if ( ! $product instanceof WC_Product ) {
			return;
		}
		$id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
		self::on_save( (int) $id );
	}

	/**
	 * Remove tracking rows for a permanently deleted product.
	 *
	 * @param int $post_id Post ID.
	 * @return void
	 */
	public static function on_delete( $post_id ) {
		if ( 'product' !== get_post_type( $post_id ) ) {
			return;
		}
		SCWC_Assigner::cleanup_product( (int) $post_id );
	}

	/**
	 * Re-evaluate a product restored from trash (untrashed_post fires for every
	 * post type, so filter to products first).
	 *
	 * @param int $post_id Post ID.
	 * @return void
	 */
	public static function on_untrash( $post_id ) {
		if ( 'product' !== get_post_type( $post_id ) ) {
			return;
		}
		self::on_save( (int) $post_id );
	}

	/**
	 * Detach managed products when a smart category term is deleted.
	 *
	 * @param int    $term_id  Term ID.
	 * @param string $taxonomy Taxonomy.
	 * @return void
	 */
	public static function on_term_delete( $term_id, $taxonomy ) {
		if ( ! in_array( $taxonomy, SCWC_Term_Rules::taxonomies(), true ) ) {
			return;
		}
		SCWC_Assigner::cleanup_term( (int) $term_id );
		SCWC_Term_Rules::clear_smart_term_cache();
	}
}
