<?php
/**
 * Event-driven Schema Health cache invalidation.
 *
 * @package SEOvaultSEO\Schema\Validation
 */

namespace SEOvaultSEO\Schema\Validation;

use SEOvaultSEO\Core\PostMeta;
use SEOvaultSEO\Schema\SchemaBuildCache;
use SEOvaultSEO\Schema\SchemaTemplate;

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

/**
 * Invalidates local/live validation caches for schema-relevant changes only.
 *
 * Sitewide schema settings use a generation counter (lazy miss) instead of
 * scanning every post to delete meta.
 */
class SchemaValidationInvalidator {

	/**
	 * Suppress invalidation while writing validation cache meta.
	 *
	 * @var int
	 */
	private static $quiet = 0;

	/**
	 * Prevent re-entrant invalidation work.
	 *
	 * @var bool
	 */
	private static $running = false;

	/**
	 * Schema-relevant post meta keys (with PostMeta::PREFIX where applicable).
	 *
	 * @var string[]
	 */
	private static $post_meta_keys = array(
		'_seovault_schema_json',
		'_seovault_schema_json_meta',
		'_seovault_schema_json_mode',
		'_seovault_schema_json_last_valid',
		'_seovault_schema_instances_v2',
		'_seovault_schema_post_migrated_v2',
		'_seovault_canonical_url',
		'_thumbnail_id',
	);

	/**
	 * WooCommerce product meta that can affect Product schema output.
	 *
	 * @var string[]
	 */
	private static $woo_meta_keys = array(
		'_price',
		'_regular_price',
		'_sale_price',
		'_sku',
		'_stock',
		'_stock_status',
		'_manage_stock',
		'_weight',
		'_global_unique_id',
	);

	/**
	 * Register invalidation hooks.
	 *
	 * @return void
	 */
	public static function init() {
		add_action( 'save_post', array( __CLASS__, 'on_save_post' ), 15, 2 );
		add_action( 'post_updated', array( __CLASS__, 'on_post_updated' ), 10, 3 );
		add_action( 'delete_post', array( __CLASS__, 'on_delete_post' ), 10, 1 );

		add_action( 'added_post_meta', array( __CLASS__, 'on_added_post_meta' ), 10, 4 );
		add_action( 'updated_post_meta', array( __CLASS__, 'on_updated_post_meta' ), 10, 4 );
		add_action( 'deleted_post_meta', array( __CLASS__, 'on_deleted_post_meta' ), 10, 4 );

		add_action( 'save_post_' . SchemaTemplate::POST_TYPE, array( __CLASS__, 'on_schema_template_saved' ), 20, 2 );
		add_action( 'before_delete_post', array( __CLASS__, 'on_before_delete_post' ), 10, 1 );

		add_action( 'update_option_seovaultseo_schemas', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'add_option_seovaultseo_schemas', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_schema_enabled', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_output_mode', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_schema_architecture_version', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_schema_publisher_identity', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_schema_automatic_components', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_schema_catalogue_presentation', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
		add_action( 'update_option_seovaultseo_schema_settings_version', array( __CLASS__, 'on_schema_option_updated' ), 10, 0 );
	}

	/**
	 * Begin a quiet section (cache writes must not re-invalidate).
	 *
	 * @return void
	 */
	public static function begin_quiet() {
		++self::$quiet;
	}

	/**
	 * End a quiet section.
	 *
	 * @return void
	 */
	public static function end_quiet() {
		if ( self::$quiet > 0 ) {
			--self::$quiet;
		}
	}

	/**
	 * Whether invalidation is currently suppressed.
	 *
	 * @return bool
	 */
	public static function is_quiet() {
		return self::$quiet > 0;
	}

	/**
	 * Bump the sitewide generation counter.
	 *
	 * @return int
	 */
	public static function bump_generation() {
		return SchemaValidationCache::bump_generation();
	}

	/**
	 * Invalidate local + live cache for one post.
	 *
	 * @param int $post_id Post ID.
	 * @return void
	 */
	public static function invalidate_post( $post_id ) {
		$post_id = (int) $post_id;
		if ( $post_id <= 0 || self::is_quiet() || self::$running ) {
			return;
		}
		if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
			return;
		}

		self::$running = true;
		self::begin_quiet();
		try {
			SchemaValidationCache::delete_all_for_post( $post_id );
			SchemaBuildCache::flush_request();
		} finally {
			self::end_quiet();
			self::$running = false;
		}
	}

	/**
	 * Clear live cache early on save so stale published-page results do not linger.
	 * Local cache is rewritten by PostSchemaValidationService at priority 20.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post object.
	 * @return void
	 */
	public static function on_save_post( $post_id, $post ) {
		if ( self::is_quiet() || ! $post instanceof \WP_Post ) {
			return;
		}
		if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
			return;
		}
		if ( SchemaTemplate::POST_TYPE === $post->post_type ) {
			return;
		}

		self::invalidate_post( $post_id );
	}

	/**
	 * Invalidate when title/content/excerpt/author change.
	 *
	 * @param int      $post_id     Post ID.
	 * @param \WP_Post $post_after  Post after update.
	 * @param \WP_Post $post_before Post before update.
	 * @return void
	 */
	public static function on_post_updated( $post_id, $post_after, $post_before ) {
		if ( self::is_quiet() || ! $post_after instanceof \WP_Post || ! $post_before instanceof \WP_Post ) {
			return;
		}
		if ( SchemaTemplate::POST_TYPE === $post_after->post_type ) {
			return;
		}
		if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
			return;
		}

		$changed = (
			(string) $post_after->post_title !== (string) $post_before->post_title
			|| (string) $post_after->post_content !== (string) $post_before->post_content
			|| (string) $post_after->post_excerpt !== (string) $post_before->post_excerpt
			|| (int) $post_after->post_author !== (int) $post_before->post_author
		);

		if ( $changed ) {
			self::invalidate_post( $post_id );
		}
	}

	/**
	 * Clear cache when a post is deleted.
	 *
	 * @param int $post_id Post ID.
	 * @return void
	 */
	public static function on_delete_post( $post_id ) {
		self::invalidate_post( $post_id );
	}

	/**
	 * @param int    $meta_id    Meta ID.
	 * @param int    $post_id    Post ID.
	 * @param string $meta_key   Meta key.
	 * @param mixed  $meta_value Meta value.
	 * @return void
	 */
	public static function on_added_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {
		unset( $meta_id, $meta_value );
		self::maybe_invalidate_for_meta( $post_id, $meta_key );
	}

	/**
	 * @param int    $meta_id    Meta ID.
	 * @param int    $post_id    Post ID.
	 * @param string $meta_key   Meta key.
	 * @param mixed  $meta_value Meta value.
	 * @return void
	 */
	public static function on_updated_post_meta( $meta_id, $post_id, $meta_key, $meta_value ) {
		unset( $meta_id, $meta_value );
		self::maybe_invalidate_for_meta( $post_id, $meta_key );
	}

	/**
	 * @param string[] $meta_ids   Meta IDs.
	 * @param int      $post_id    Post ID.
	 * @param string   $meta_key   Meta key.
	 * @param mixed    $meta_value Meta value.
	 * @return void
	 */
	public static function on_deleted_post_meta( $meta_ids, $post_id, $meta_key, $meta_value ) {
		unset( $meta_ids, $meta_value );
		self::maybe_invalidate_for_meta( $post_id, $meta_key );
	}

	/**
	 * Schema template CPT save → sitewide generation bump.
	 *
	 * @param int      $post_id Post ID.
	 * @param \WP_Post $post    Post.
	 * @return void
	 */
	public static function on_schema_template_saved( $post_id, $post ) {
		unset( $post_id );
		if ( self::is_quiet() || ! $post instanceof \WP_Post ) {
			return;
		}
		if ( wp_is_post_autosave( $post->ID ) || wp_is_post_revision( $post->ID ) ) {
			return;
		}
		self::bump_generation();
		SchemaBuildCache::flush_request();
	}

	/**
	 * Schema template delete → sitewide generation bump.
	 *
	 * @param int $post_id Post ID.
	 * @return void
	 */
	public static function on_before_delete_post( $post_id ) {
		if ( self::is_quiet() ) {
			return;
		}
		$post = get_post( $post_id );
		if ( ! $post instanceof \WP_Post || SchemaTemplate::POST_TYPE !== $post->post_type ) {
			return;
		}
		self::bump_generation();
		SchemaBuildCache::flush_request();
	}

	/**
	 * Schema-relevant plugin options changed.
	 *
	 * @return void
	 */
	public static function on_schema_option_updated() {
		if ( self::is_quiet() ) {
			return;
		}
		self::bump_generation();
		SchemaBuildCache::flush_request();
	}

	/**
	 * Invalidate when a schema-relevant meta key changes.
	 *
	 * @param int    $post_id  Post ID.
	 * @param string $meta_key Meta key.
	 * @return void
	 */
	private static function maybe_invalidate_for_meta( $post_id, $meta_key ) {
		if ( self::is_quiet() || ! is_string( $meta_key ) || '' === $meta_key ) {
			return;
		}
		if ( in_array( $meta_key, SchemaValidationCache::ignored_meta_keys(), true ) ) {
			return;
		}

		$watched = self::$post_meta_keys;
		if ( 'product' === get_post_type( $post_id ) ) {
			$watched = array_merge( $watched, self::$woo_meta_keys );
		}

		if ( in_array( $meta_key, $watched, true ) ) {
			self::invalidate_post( $post_id );
		}
	}
}
