<?php
/**
 * Frontend Query Exclusion — Search-only noindex content exclusion.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Frontend;

use SEOvaultSEO\Core\Options;
use SEOvaultSEO\Core\PostMeta;
use SEOvaultSEO\Compatibility\Detector;

/**
 * Excludes noindex content from WordPress search results.
 *
 * Controlled by the seovaultseo_exclude_noindex_from_search option.
 * Only affects search queries in this phase — archive exclusion is not implemented.
 */
class QueryExclusion {

	/**
	 * Transient key for cached excluded post IDs.
	 *
	 * @var string
	 */
	const CACHE_KEY = 'seovaultseo_excluded_search_ids';

	/**
	 * Initialize the query exclusion hooks.
	 */
	public static function init() {
		if ( ! Detector::should_output_seo() ) {
			return;
		}

		if ( ! Options::is_exclude_noindex_from_search() ) {
			return;
		}

		add_action( 'pre_get_posts', array( __CLASS__, 'exclude_noindex_from_search' ) );

		// Cache invalidation hooks.
		add_action( 'added_post_meta', array( __CLASS__, 'maybe_clear_cache' ), 10, 4 );
		add_action( 'updated_post_meta', array( __CLASS__, 'maybe_clear_cache' ), 10, 4 );
		add_action( 'deleted_post_meta', array( __CLASS__, 'maybe_clear_cache' ), 10, 4 );
	}

	/**
	 * Exclude noindex posts from search results.
	 *
	 * @param \WP_Query $query The WP_Query object.
	 */
	public static function exclude_noindex_from_search( $query ) {
		if ( ! $query->is_search() ) {
			return;
		}

		if ( is_admin() ) {
			return;
		}

		$excluded_ids = self::get_excluded_post_ids();

		if ( empty( $excluded_ids ) ) {
			return;
		}

		$post_not_in = $query->get( 'post__not_in' );

		if ( ! is_array( $post_not_in ) ) {
			$post_not_in = array();
		}

		$query->set( 'post__not_in', array_merge( $post_not_in, $excluded_ids ) );
	}

	/**
	 * Get the post IDs that have noindex set and should be excluded from search.
	 *
	 * Uses a transient cache to avoid per-request queries.
	 *
	 * @return array Array of post IDs.
	 */
	public static function get_excluded_post_ids() {
		$cached = get_transient( self::CACHE_KEY );

		if ( false !== $cached && is_array( $cached ) ) {
			return $cached;
		}

		global $wpdb;

		$meta_key = PostMeta::PREFIX . 'robots_noindex';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$results = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = 1",
				$meta_key
			)
		);

		$ids = array_map( 'intval', $results );

		set_transient( self::CACHE_KEY, $ids, DAY_IN_SECONDS );

		return $ids;
	}

	/**
	 * Clear the excluded IDs cache when relevant post meta changes.
	 *
	 * @param int    $meta_id   Meta ID.
	 * @param int    $post_id   Post ID.
	 * @param string $meta_key  Meta key.
	 * @param mixed  $meta_value Meta value.
	 */
	public static function maybe_clear_cache( $meta_id, $post_id, $meta_key, $meta_value ) {
		if ( PostMeta::PREFIX . 'robots_noindex' === $meta_key ) {
			delete_transient( self::CACHE_KEY );
		}
	}

	/**
	 * Force-clear the cache. Useful for tests and manual invalidation.
	 */
	public static function clear_cache() {
		delete_transient( self::CACHE_KEY );
	}
}
