<?php
/**
 * Display Conditions
 *
 * Evaluates whether a schema template should be displayed on the
 * current page based on include/exclude rules (post type, taxonomy,
 * specific posts, archives, user roles, etc.).
 *
 * @package SEOvaultSEO\Schema
 */

namespace SEOvaultSEO\Schema;

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

class DisplayConditions {

	/**
	 * Evaluate whether a schema should be shown based on its display conditions.
	 *
	 * @param array                           $schema Schema data with optional metadata.displayConditions.
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Explicit object for non-frontend evaluation.
	 * @return bool True if schema should be displayed.
	 */
	public static function evaluate( $schema, $object = null ) {
		$conditions = $schema['metadata']['displayConditions'] ?? array();

		if ( empty( $conditions ) ) {
			return false;
		}

		$includes = array();
		$excludes = array();

		foreach ( $conditions as $condition ) {
			$operator = $condition['condition'] ?? 'include';
			if ( 'exclude' === $operator ) {
				$excludes[] = $condition;
			} else {
				$includes[] = $condition;
			}
		}

		// Check excludes first — if any match, hide.
		foreach ( $excludes as $exclude ) {
			if ( self::matches_condition( $exclude, $object ) ) {
				return false;
			}
		}

		// If no includes, show by default (only excludes defined).
		if ( empty( $includes ) ) {
			return true;
		}

		// Check includes — if any match, show.
		foreach ( $includes as $include ) {
			if ( self::matches_condition( $include, $object ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Check if a single condition matches the current page.
	 *
	 * @param array                           $condition Condition data.
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Explicit object for non-frontend evaluation.
	 * @return bool
	 */
	public static function matches_condition( $condition, $object = null ) {
		$category = $condition['category'] ?? 'general';
		$type     = $condition['type'] ?? 'all';
		$value    = (int) ( $condition['value'] ?? 0 );
		$taxonomy = $condition['postTaxonomy'] ?? '';

		if ( 'general' === $category ) {
			return self::match_general( $type );
		}

		if ( 'singular' === $category ) {
			return self::match_singular( $type, $value, $taxonomy, $object );
		}

		if ( 'archive' === $category ) {
			return self::match_archive( $type, $value );
		}

		return false;
	}

	/**
	 * Match general conditions (entire site, front page, etc.).
	 *
	 * @param string $type Condition type.
	 * @return bool
	 */
	private static function match_general( $type ) {
		if ( 'all' === $type ) {
			return true;
		}
		if ( 'front' === $type ) {
			return is_front_page();
		}
		if ( 'blog' === $type ) {
			// Posts page when a static front page is configured; otherwise home blog index.
			return is_home();
		}
		if ( 'search' === $type && is_search() ) {
			return true;
		}
		if ( '404' === $type && is_404() ) {
			return true;
		}
		return false;
	}

	/**
	 * Match singular (single post) conditions.
	 *
	 * @param string                          $type     Post type or 'all'.
	 * @param int                             $value    Post ID (0 = all of type).
	 * @param string                          $taxonomy Taxonomy name for term-based conditions.
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Explicit object for non-frontend evaluation.
	 * @return bool
	 */
	private static function match_singular( $type, $value, $taxonomy, $object = null ) {
		if ( ! $object instanceof \WP_Post && ! is_singular() && ! is_admin() ) {
			return false;
		}

		$post = null;
		if ( $object instanceof \WP_Post ) {
			$post = $object;
		} else {
			$post_id = get_the_ID();
			$post    = $post_id ? get_post( $post_id ) : null;
		}

		if ( ! $post instanceof \WP_Post ) {
			return false;
		}

		// Deleted / missing posts never match. Private posts only match when an
		// explicit object is supplied (editor preview) or the current user can read them.
		if ( 'trash' === $post->post_status || 'auto-draft' === $post->post_status ) {
			return false;
		}
		if ( 'private' === $post->post_status && ! ( $object instanceof \WP_Post ) && ! is_user_logged_in() ) {
			return false;
		}
		if ( 'private' === $post->post_status && ! ( $object instanceof \WP_Post ) && ! current_user_can( 'read_post', $post->ID ) ) {
			return false;
		}

		$post_id = (int) $post->ID;

		if ( 'all' === $type ) {
			return true;
		}

		$post_type = get_post_type( $post );
		if ( $type !== $post_type ) {
			return false;
		}

		if ( $taxonomy ) {
			if ( $value <= 0 ) {
				return false;
			}
			$term = get_term( $value, $taxonomy );
			if ( ! $term || is_wp_error( $term ) ) {
				return false;
			}
			return has_term( $value, $taxonomy, $post_id );
		}

		if ( $value > 0 ) {
			$target = get_post( $value );
			if ( ! $target instanceof \WP_Post ) {
				return false;
			}
			return absint( $post_id ) === $value;
		}

		return true;
	}

	/**
	 * Match archive conditions.
	 *
	 * @param string $type  Archive type (author, category, post_tag, taxonomy, all).
	 * @param int    $value Term/user ID (0 = all of type).
	 * @return bool
	 */
	private static function match_archive( $type, $value ) {
		if ( 'all' === $type ) {
			return is_archive();
		}

		if ( 'author' === $type ) {
			if ( ! is_author() ) {
				return false;
			}
			return ! $value || is_author( $value );
		}

		if ( 'category' === $type ) {
			if ( ! is_category() ) {
				return false;
			}
			return ! $value || is_category( $value );
		}

		if ( 'post_tag' === $type ) {
			if ( ! is_tag() ) {
				return false;
			}
			return ! $value || is_tag( $value );
		}

		if ( is_tax( $type ) ) {
			$object_id = get_queried_object_id();
			return ! $value || absint( $object_id ) === $value;
		}

		return false;
	}

	/**
	 * Get schema templates from custom post type that match current page.
	 *
	 * @param \WP_Post|\WP_Term|\WP_User|null $object Explicit object for non-frontend evaluation.
	 * @return array Matching schema data arrays.
	 */
	public static function get_matching_templates( $object = null ) {
		$templates = get_posts(
			array(
				'post_type'      => 'seovault_schema',
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Schema templates stored in post meta by design.
				'meta_key'       => '_seovault_schema_data',
			)
		);

		if ( empty( $templates ) ) {
			return array();
		}

		$matching = array();
		foreach ( $templates as $post ) {
			$schema_data = SchemaTemplate::get_template( $post->ID );
			if ( empty( $schema_data ) || ! is_array( $schema_data ) ) {
				continue;
			}

			if ( self::evaluate( $schema_data, $object ) ) {
				$schema_data['template_id'] = $post->ID;
				$matching[]                 = $schema_data;
			}
		}

		return $matching;
	}
}
