<?php
/**
 * Device targeting and conditional logic for the Pro Code Snippet module.
 *
 * Secure design (rafter-secure-design):
 * - Identity: site visitors (frontend) and admins (admin-only snippets).
 * - AuthZ: The whole Code Snippet module is Pro. Lite never ships this file.
 * - Untrusted input: rule values are sanitized on save in Module.
 * - Geo/IP: request headers / REMOTE_ADDR only. No outbound GeoIP fetch, no trusted X-Forwarded-For by default.
 * - Sessions: read an existing session only — never start one.
 *
 * @package CodeSnippet
 */

namespace CodeSnippet;

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

/**
 * Evaluates device and conditional snippet rules.
 */
final class PremiumTargeting {

	/**
	 * Snippet IDs that already ran on this request.
	 *
	 * @var array<int, true>
	 */
	private static array $loaded_snippet_ids = [];

	public static function register(): void {
		add_action( 'wpps_snippet_loaded', [ self::class, 'trackLoadedSnippet' ], 10, 1 );
	}

	/**
	 * @param \WP_Post|int $post Snippet post or ID.
	 */
	public static function trackLoadedSnippet( $post ): void {
		$id = $post instanceof \WP_Post ? (int) $post->ID : (int) $post;
		if ( $id > 0 ) {
			self::$loaded_snippet_ids[ $id ] = true;
		}
	}

	/**
	 * @param \WP_Post $post Snippet post.
	 */
	public static function passesDeviceCheck( \WP_Post $post ): bool {
		$device = get_post_meta( $post->ID, '_wpps_device_type', true ) ?: 'all';
		if ( $device === 'all' ) {
			return true;
		}
		return self::deviceMatches( (string) $device );
	}

	/**
	 * @param \WP_Post $post Snippet post.
	 */
	public static function passesConditionalLogic( \WP_Post $post ): bool {
		$enabled = get_post_meta( $post->ID, '_wpps_conditional_enabled', true ) === '1';
		if ( ! $enabled ) {
			return true;
		}
		$rules = get_post_meta( $post->ID, '_wpps_conditional_rules', true );
		if ( ! is_array( $rules ) || empty( $rules ) ) {
			return true;
		}
		$show = ( $rules['show'] ?? get_post_meta( $post->ID, '_wpps_conditional_show', true ) ) !== '0';
		$pass = self::evaluateConditionalRules( $rules );
		return $show ? $pass : ! $pass;
	}

	/**
	 * Whether any enabled rule needs the resolved main query.
	 *
	 * @param \WP_Post $post Snippet post.
	 */
	public static function conditionalRulesNeedQuery( \WP_Post $post ): bool {
		if ( get_post_meta( $post->ID, '_wpps_conditional_enabled', true ) !== '1' ) {
			return false;
		}
		$rules = get_post_meta( $post->ID, '_wpps_conditional_rules', true );
		if ( ! is_array( $rules ) || empty( $rules ) ) {
			return false;
		}
		$groups = $rules['groups'] ?? [ self::normalizeRulesArray( $rules ) ];
		if ( ! is_array( $groups ) ) {
			return false;
		}
		foreach ( $groups as $group ) {
			foreach ( self::normalizeRulesArray( is_array( $group ) ? $group : [] ) as $rule ) {
				if ( ConditionalCatalog::isQueryType( (string) ( $rule['type'] ?? '' ) ) ) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * @param array<string, mixed> $rules
	 */
	private static function evaluateConditionalRules( array $rules ): bool {
		$groups = $rules['groups'] ?? null;
		if ( $groups === null ) {
			$groups = [ self::normalizeRulesArray( $rules ) ];
		}
		if ( empty( $groups ) ) {
			return true;
		}
		foreach ( $groups as $group ) {
			if ( self::evaluateRuleGroup( is_array( $group ) ? $group : [] ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @param array<mixed> $group
	 */
	private static function evaluateRuleGroup( array $group ): bool {
		$group = self::normalizeRulesArray( $group );
		if ( $group === [] ) {
			return true;
		}
		foreach ( $group as $rule ) {
			if ( ! self::evaluateRule( $rule ) ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @param array<mixed> $rules
	 * @return list<array<string, mixed>>
	 */
	private static function normalizeRulesArray( array $rules ): array {
		$out = [];
		foreach ( $rules as $k => $v ) {
			if ( $k === 'groups' || $k === 'show' ) {
				continue;
			}
			if ( is_array( $v ) && isset( $v['type'] ) ) {
				$out[] = $v;
			}
		}
		return $out;
	}

	/**
	 * @param array<string, mixed> $rule
	 */
	public static function evaluateRule( array $rule ): bool {
		$type     = (string) ( $rule['type'] ?? '' );
		$operator = (string) ( $rule['operator'] ?? 'contains' );
		$value    = (string) ( $rule['value'] ?? '' );
		$key      = (string) ( $rule['key'] ?? '' );

		switch ( $type ) {
			case 'url':
				$current = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
				return self::compareString( $current, $operator, $value );
			case 'full_url':
				return self::compareString( self::currentUrl(), $operator, $value );
			case 'url_path':
				$path = (string) wp_parse_url( self::currentUrl(), PHP_URL_PATH );
				return self::compareString( $path !== '' ? $path : '/', $operator, $value );
			case 'user_role':
				if ( ! function_exists( 'wp_get_current_user' ) || ! function_exists( 'is_user_logged_in' ) ) {
					return false;
				}
				if ( ! is_user_logged_in() ) {
					return in_array( $operator, [ 'not_equals', 'not_contains' ], true );
				}
				$user = wp_get_current_user();
				$has  = in_array( $value, (array) $user->roles, true );
				return in_array( $operator, [ 'equals', 'contains' ], true ) ? $has : ! $has;
			case 'logged_in':
				if ( ! function_exists( 'is_user_logged_in' ) ) {
					return false;
				}
				$logged_in = is_user_logged_in();
				$wanted    = in_array( $value, [ '1', 'yes', 'logged_in' ], true );
				$match     = $logged_in === $wanted;
				return in_array( $operator, [ 'equals', 'contains' ], true ) ? $match : ! $match;
			case 'user_meta':
				if ( $key === '' || ! is_user_logged_in() ) {
					return self::compareExists( false, $operator );
				}
				$meta = get_user_meta( get_current_user_id(), $key, true );
				$str  = is_scalar( $meta ) ? (string) $meta : '';
				if ( in_array( $operator, [ 'exists', 'not_exists', 'is_empty', 'is_not_empty' ], true ) ) {
					return self::compareExists( $meta !== '' && $meta !== false, $operator );
				}
				return self::compareString( $str, $operator, $value );
			case 'device':
				return self::applyEquality( self::deviceMatches( $value ), $operator );
			case 'browser':
				return self::applyEquality( self::detectBrowser() === $value, $operator );
			case 'os':
				return self::applyEquality( self::detectOs() === $value, $operator );
			case 'cookie':
				$cookies = self::requestCookies();
				$exists  = $key !== '' && array_key_exists( $key, $cookies );
				if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
					return self::compareExists( $exists, $operator );
				}
				$cookie_val = $exists ? (string) $cookies[ $key ] : '';
				return self::compareString( $cookie_val, $operator, $value );
			case 'country':
				$country = self::visitorCountry();
				return self::compareString( $country, $operator, strtoupper( $value ) );
			case 'continent':
				return self::applyEquality( self::visitorContinent() === strtoupper( $value ), $operator );
			case 'session':
				if ( $key === '' || ! isset( $_SESSION ) || ! is_array( $_SESSION ) ) {
					return self::compareExists( false, $operator );
				}
				$exists = array_key_exists( $key, $_SESSION );
				if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
					return self::compareExists( $exists, $operator );
				}
				$session_val = $exists && is_scalar( $_SESSION[ $key ] ) ? (string) $_SESSION[ $key ] : '';
				return self::compareString( $session_val, $operator, $value );
			case 'type_of_page':
				if ( ! did_action( 'wp' ) ) {
					return false;
				}
				return self::applyEquality( self::pageTypeMatches( $value ), $operator );
			case 'post_type':
				if ( ! did_action( 'wp' ) ) {
					return false;
				}
				return self::applyEquality( self::getCurrentPostType() === $value, $operator );
			case 'taxonomy_archive':
				if ( ! did_action( 'wp' ) ) {
					return false;
				}
				return self::applyEquality( is_tax( $value ) || is_category() && $value === 'category' || is_tag() && $value === 'post_tag', $operator );
			case 'taxonomy':
				if ( ! did_action( 'wp' ) ) {
					return false;
				}
				return self::applyEquality( self::currentTaxonomy() === $value, $operator );
			case 'term':
				if ( ! did_action( 'wp' ) ) {
					return false;
				}
				return self::compareString( self::currentTermHaystack(), $operator, $value );
			case 'post_meta':
				$post = self::queriedPost();
				if ( ! $post || $key === '' ) {
					return self::compareExists( false, $operator );
				}
				$meta = get_post_meta( $post->ID, $key, true );
				if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
					return self::compareExists( metadata_exists( 'post', $post->ID, $key ), $operator );
				}
				$str = is_scalar( $meta ) ? (string) $meta : '';
				return self::compareString( $str, $operator, $value );
			case 'post_status':
				$post = self::queriedPost();
				return self::applyEquality( $post ? $post->post_status === $value : false, $operator );
			case 'post_id':
				$post = self::queriedPost();
				$ids  = array_filter( array_map( 'absint', explode( ',', $value ) ) );
				$has  = $post instanceof \WP_Post && in_array( (int) $post->ID, $ids, true );
				return self::applyEquality( $has, $operator );
			case 'page_template':
				$post = self::queriedPost();
				$tpl  = $post ? (string) get_page_template_slug( $post ) : '';
				return self::applyEquality( $tpl === $value, $operator );
			case 'author':
				$post = self::queriedPost();
				if ( ! $post ) {
					return self::applyEquality( false, $operator );
				}
				$author = get_userdata( (int) $post->post_author );
				$hay    = $author ? (string) $post->post_author . ' ' . $author->user_login : (string) $post->post_author;
				return self::compareString( $hay, $operator, $value );
			case 'referer':
				$ref = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '';
				return self::compareString( $ref, $operator, $value );
			case 'user_agent':
				return self::compareString( self::userAgent(), $operator, $value );
			case 'query_var':
				$qs = [];
				$query = (string) wp_parse_url( self::currentUrl(), PHP_URL_QUERY );
				if ( $query !== '' ) {
					parse_str( $query, $qs );
				}
				$exists = $key !== '' && array_key_exists( $key, $qs );
				if ( in_array( $operator, [ 'exists', 'not_exists' ], true ) ) {
					return self::compareExists( $exists, $operator );
				}
				$qval = $exists && is_scalar( $qs[ $key ] ) ? (string) $qs[ $key ] : '';
				return self::compareString( $qval, $operator, $value );
			case 'wc_page':
				return self::applyEquality( self::isWooPage( $value ), $operator );
			case 'wc_order_count':
				return self::compareNumber( self::wooOrderCount(), $operator, $value );
			case 'wc_lifetime_spend':
				return self::compareNumber( self::wooLifetimeSpend(), $operator, $value );
			case 'wc_cart_total':
				return self::compareNumber( self::wooCartTotal(), $operator, $value );
			case 'wc_product_in_cart':
				return self::applyEquality( self::wooProductInCart( (int) $value ), $operator );
			case 'edd_page':
				return self::applyEquality( self::isEddPage( $value ), $operator );
			case 'mepr_page':
				return self::applyEquality( self::isMemberPressPage( $value ), $operator );
			case 'mepr_user':
				return self::applyEquality( self::memberPressStatus() === $value, $operator );
			case 'date':
				return self::compareDateTime( wp_date( 'Y-m-d' ), $operator, $value, 'Y-m-d' );
			case 'datetime':
				return self::compareDateTime( wp_date( 'Y-m-d H:i' ), $operator, str_replace( 'T', ' ', $value ), 'Y-m-d H:i' );
			case 'day_of_week':
				return self::applyEquality( (string) wp_date( 'w' ) === (string) $value, $operator );
			case 'time_of_day':
				return self::compareDateTime( wp_date( 'H:i' ), $operator, $value, 'H:i' );
			case 'snippet_loaded':
				$id = absint( $value );
				return self::applyEquality( $id > 0 && isset( self::$loaded_snippet_ids[ $id ] ), $operator );
			case 'ip':
				return self::applyEquality( self::ipMatches( self::visitorIp(), $value ), $operator );
			default:
				return true;
		}
	}

	private static function applyEquality( bool $match, string $operator ): bool {
		return in_array( $operator, [ 'equals', 'contains' ], true ) ? $match : ! $match;
	}

	private static function compareExists( bool $exists, string $operator ): bool {
		if ( $operator === 'not_exists' || $operator === 'is_empty' ) {
			return ! $exists;
		}
		return $exists;
	}

	private static function compareString( string $current, string $operator, string $needle ): bool {
		if ( $operator === 'is_empty' ) {
			return $current === '';
		}
		if ( $operator === 'is_not_empty' ) {
			return $current !== '';
		}
		$match = self::wildcardOrContains( $current, $needle, $operator );
		if ( in_array( $operator, [ 'equals', 'contains' ], true ) ) {
			return $match;
		}
		if ( in_array( $operator, [ 'not_equals', 'not_contains' ], true ) ) {
			return ! $match;
		}
		return $match;
	}

	private static function wildcardOrContains( string $current, string $needle, string $operator ): bool {
		if ( $needle === '' ) {
			return $operator === 'contains' || $operator === 'equals';
		}
		if ( in_array( $operator, [ 'equals', 'not_equals' ], true ) && ! str_contains( $needle, '*' ) ) {
			return strcasecmp( $current, $needle ) === 0;
		}
		if ( str_contains( $needle, '*' ) ) {
			$pattern = '/^' . str_replace( '\*', '.*', preg_quote( $needle, '/' ) ) . '$/i';
			return (bool) preg_match( $pattern, $current );
		}
		return stripos( $current, $needle ) !== false;
	}

	private static function compareNumber( float $current, string $operator, string $needle ): bool {
		$target = (float) $needle;
		switch ( $operator ) {
			case 'greater_than':
				return $current > $target;
			case 'less_than':
				return $current < $target;
			case 'not_equals':
				return abs( $current - $target ) > 0.00001;
			default:
				return abs( $current - $target ) <= 0.00001;
		}
	}

	private static function compareDateTime( string $current, string $operator, string $target, string $format ): bool {
		$cur = \DateTimeImmutable::createFromFormat( $format, $current );
		$tgt = \DateTimeImmutable::createFromFormat( $format, $target );
		if ( ! $cur || ! $tgt ) {
			return self::compareString( $current, $operator, $target );
		}
		if ( $operator === 'before' ) {
			return $cur < $tgt;
		}
		if ( $operator === 'after' ) {
			return $cur > $tgt;
		}
		$equal = $cur == $tgt;
		return $operator === 'not_equals' ? ! $equal : $equal;
	}

	private static function userAgent(): string {
		return isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
	}

	private static function deviceMatches( string $device ): bool {
		$ua = self::userAgent();
		if ( $ua === '' ) {
			return $device === 'desktop';
		}
		$is_mobile = (bool) preg_match( '/Mobile|Android|iPhone|iPad|iPod|webOS|IEMobile/i', $ua );
		$is_tablet = (bool) preg_match( '/iPad|Android(?!.*Mobile)|Tablet|Silk/i', $ua );
		switch ( $device ) {
			case 'mobile':
				return $is_mobile && ! $is_tablet;
			case 'tablet':
				return $is_tablet;
			case 'desktop':
				return ! $is_mobile && ! $is_tablet;
			default:
				return false;
		}
	}

	private static function detectBrowser(): string {
		$ua = self::userAgent();
		if ( $ua === '' ) {
			return '';
		}
		if ( preg_match( '/Edg\//i', $ua ) ) {
			return 'edge';
		}
		if ( preg_match( '/OPR\/|Opera/i', $ua ) ) {
			return 'opera';
		}
		if ( preg_match( '/Chrome\//i', $ua ) && ! preg_match( '/Chromium/i', $ua ) ) {
			return 'chrome';
		}
		if ( preg_match( '/Safari\//i', $ua ) && ! preg_match( '/Chrome/i', $ua ) ) {
			return 'safari';
		}
		if ( preg_match( '/Firefox\//i', $ua ) ) {
			return 'firefox';
		}
		if ( preg_match( '/MSIE |Trident\//i', $ua ) ) {
			return 'ie';
		}
		return '';
	}

	private static function detectOs(): string {
		$ua = self::userAgent();
		if ( preg_match( '/iPhone|iPad|iPod/i', $ua ) ) {
			return 'ios';
		}
		if ( preg_match( '/Android/i', $ua ) ) {
			return 'android';
		}
		if ( preg_match( '/Windows/i', $ua ) ) {
			return 'windows';
		}
		if ( preg_match( '/Mac OS X|Macintosh/i', $ua ) ) {
			return 'macos';
		}
		if ( preg_match( '/Linux/i', $ua ) ) {
			return 'linux';
		}
		return '';
	}

	/**
	 * @return array<string, string>
	 */
	private static function requestCookies(): array {
		$out = [];
		if ( ! isset( $_COOKIE ) || ! is_array( $_COOKIE ) ) {
			return $out;
		}
		foreach ( $_COOKIE as $name => $val ) {
			if ( is_scalar( $val ) ) {
				$out[ (string) $name ] = (string) $val;
			}
		}
		return $out;
	}

	private static function currentUrl(): string {
		if ( function_exists( 'home_url' ) && isset( $_SERVER['REQUEST_URI'] ) ) {
			return home_url( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
		}
		return isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
	}

	private static function visitorCountry(): string {
		$headers = [
			'HTTP_CF_IPCOUNTRY',
			'HTTP_X_COUNTRY_CODE',
			'HTTP_CLOUDFRONT_VIEWER_COUNTRY',
			'GEOIP_COUNTRY_CODE',
		];
		$iso = '';
		foreach ( $headers as $header ) {
			if ( ! empty( $_SERVER[ $header ] ) ) {
				$iso = strtoupper( sanitize_text_field( wp_unslash( (string) $_SERVER[ $header ] ) ) );
				break;
			}
		}
		$filtered = apply_filters( 'wpps_code_snippet_visitor_country', $iso );
		return is_string( $filtered ) ? strtoupper( $filtered ) : $iso;
	}

	private static function visitorContinent(): string {
		$country = self::visitorCountry();
		if ( $country === '' ) {
			return '';
		}
		$map = apply_filters( 'wpps_code_snippet_country_continent_map', self::countryContinentMap() );
		if ( ! is_array( $map ) ) {
			$map = self::countryContinentMap();
		}
		return isset( $map[ $country ] ) ? (string) $map[ $country ] : '';
	}

	/**
	 * Compact ISO2 → continent map. Unknown countries can be supplied via filter.
	 *
	 * @return array<string, string>
	 */
	private static function countryContinentMap(): array {
		static $map = null;
		if ( is_array( $map ) ) {
			return $map;
		}
		$packed = 'US:NA CA:NA MX:NA GT:NA CU:NA DO:NA HT:NA JM:NA PR:NA PA:NA CR:NA HN:NA NI:NA SV:NA BZ:NA '
			. 'BR:SA AR:SA CO:SA CL:SA PE:SA VE:SA EC:SA BO:SA PY:SA UY:SA GY:SA SR:SA '
			. 'GB:EU IE:EU FR:EU DE:EU ES:EU IT:EU PT:EU NL:EU BE:EU LU:EU CH:EU AT:EU PL:EU CZ:EU SK:EU HU:EU RO:EU BG:EU GR:EU SE:EU NO:EU FI:EU DK:EU IS:EU EE:EU LV:EU LT:EU UA:EU BY:EU MD:EU RS:EU HR:EU SI:EU BA:EU MK:EU AL:EU ME:EU XK:EU RU:EU '
			. 'CN:AS JP:AS KR:AS KP:AS IN:AS PK:AS BD:AS LK:AS NP:AS MM:AS TH:AS VN:AS KH:AS LA:AS MY:AS SG:AS ID:AS PH:AS TW:AS HK:AS MO:AS MN:AS KZ:AS UZ:AS TM:AS KG:AS TJ:AS AF:AS IR:AS IQ:AS SA:AS AE:AS YE:AS OM:AS QA:AS BH:AS KW:AS JO:AS LB:AS SY:AS IL:AS PS:AS TR:AS GE:AS AM:AS AZ:AS '
			. 'EG:AF ZA:AF NG:AF KE:AF ET:AF GH:AF TZ:AF UG:AF DZ:AF MA:AF TN:AF LY:AF SD:AF SS:AF AO:AF MZ:AF ZM:AF ZW:AF BW:AF NA:AF CM:AF CI:AF SN:AF ML:AF NE:AF TD:AF CD:AF CG:AF GA:AF MG:AF MU:AF RW:AF BI:AF SO:AF DJ:AF ER:AF '
			. 'AU:OC NZ:OC PG:OC FJ:OC SB:OC VU:OC WS:OC TO:OC '
			. 'AQ:AN';
		$map = [];
		foreach ( explode( ' ', $packed ) as $pair ) {
			if ( ! str_contains( $pair, ':' ) ) {
				continue;
			}
			[ $iso, $cont ] = explode( ':', $pair, 2 );
			$map[ $iso ]    = $cont;
		}
		return $map;
	}

	private static function visitorIp(): string {
		$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) : '';
		$filtered = apply_filters( 'wpps_code_snippet_visitor_ip', $ip );
		return is_string( $filtered ) ? $filtered : $ip;
	}

	private static function ipMatches( string $ip, string $spec ): bool {
		if ( $ip === '' || $spec === '' ) {
			return false;
		}
		foreach ( array_filter( array_map( 'trim', explode( ',', $spec ) ) ) as $part ) {
			if ( str_contains( $part, '/' ) ) {
				if ( self::ipInCidr( $ip, $part ) ) {
					return true;
				}
				continue;
			}
			if ( strcasecmp( $ip, $part ) === 0 ) {
				return true;
			}
		}
		return false;
	}

	private static function ipInCidr( string $ip, string $cidr ): bool {
		if ( ! str_contains( $cidr, '/' ) ) {
			return false;
		}
		[ $subnet, $bits ] = explode( '/', $cidr, 2 );
		$bits = (int) $bits;
		$ip_bin     = @inet_pton( $ip );
		$subnet_bin = @inet_pton( $subnet );
		if ( $ip_bin === false || $subnet_bin === false || strlen( $ip_bin ) !== strlen( $subnet_bin ) ) {
			return false;
		}
		$max = strlen( $ip_bin ) * 8;
		if ( $bits < 0 || $bits > $max ) {
			return false;
		}
		$bytes = intdiv( $bits, 8 );
		$rem   = $bits % 8;
		if ( $bytes > 0 && substr( $ip_bin, 0, $bytes ) !== substr( $subnet_bin, 0, $bytes ) ) {
			return false;
		}
		if ( $rem === 0 ) {
			return true;
		}
		$mask = chr( ( 0xFF << ( 8 - $rem ) ) & 0xFF );
		return ( $ip_bin[ $bytes ] & $mask ) === ( $subnet_bin[ $bytes ] & $mask );
	}

	private static function getTypeOfPage(): string {
		if ( is_front_page() ) {
			return 'is_front_page';
		}
		if ( is_home() ) {
			return 'is_home';
		}
		if ( is_singular( 'page' ) ) {
			return 'is_page';
		}
		if ( is_singular() ) {
			return 'is_single';
		}
		if ( is_author() ) {
			return 'is_author';
		}
		if ( is_search() ) {
			return 'is_search';
		}
		if ( is_404() ) {
			return 'is_404';
		}
		if ( is_category() ) {
			return 'is_category';
		}
		if ( is_tag() ) {
			return 'is_tag';
		}
		if ( is_tax() ) {
			return 'is_tax';
		}
		if ( is_archive() ) {
			return 'is_archive';
		}
		return '';
	}

	private static function pageTypeMatches( string $wanted ): bool {
		if ( $wanted === 'is_singular' ) {
			return is_singular();
		}
		return self::getTypeOfPage() === $wanted;
	}

	private static function getCurrentPostType(): string {
		if ( is_singular() ) {
			$post = get_queried_object();
			return $post instanceof \WP_Post ? $post->post_type : '';
		}
		if ( is_post_type_archive() ) {
			$pt = get_query_var( 'post_type' );
			return is_string( $pt ) ? $pt : '';
		}
		return '';
	}

	private static function queriedPost(): ?\WP_Post {
		if ( ! did_action( 'wp' ) ) {
			return null;
		}
		$obj = get_queried_object();
		return $obj instanceof \WP_Post ? $obj : null;
	}

	private static function currentTaxonomy(): string {
		if ( is_category() ) {
			return 'category';
		}
		if ( is_tag() ) {
			return 'post_tag';
		}
		if ( is_tax() ) {
			$obj = get_queried_object();
			return $obj instanceof \WP_Term ? $obj->taxonomy : '';
		}
		return '';
	}

	private static function currentTermHaystack(): string {
		$obj = get_queried_object();
		if ( $obj instanceof \WP_Term ) {
			return $obj->slug . ' ' . $obj->name;
		}
		return '';
	}

	private static function isWooPage( string $page ): bool {
		if ( ! function_exists( 'is_woocommerce' ) ) {
			return false;
		}
		switch ( $page ) {
			case 'shop':
				return function_exists( 'is_shop' ) && is_shop();
			case 'cart':
				return function_exists( 'is_cart' ) && is_cart();
			case 'checkout':
				return function_exists( 'is_checkout' ) && is_checkout() && ! ( function_exists( 'is_order_received_page' ) && is_order_received_page() );
			case 'account':
				return function_exists( 'is_account_page' ) && is_account_page();
			case 'product':
				return function_exists( 'is_product' ) && is_product();
			case 'order_received':
				return function_exists( 'is_order_received_page' ) && is_order_received_page();
			default:
				return false;
		}
	}

	private static function wooOrderCount(): float {
		if ( ! function_exists( 'wc_get_customer_order_count' ) || ! is_user_logged_in() ) {
			return 0.0;
		}
		return (float) wc_get_customer_order_count( get_current_user_id() );
	}

	private static function wooLifetimeSpend(): float {
		if ( ! function_exists( 'wc_get_customer_total_spent' ) || ! is_user_logged_in() ) {
			return 0.0;
		}
		return (float) wc_get_customer_total_spent( get_current_user_id() );
	}

	private static function wooCartTotal(): float {
		if ( ! function_exists( 'WC' ) ) {
			return 0.0;
		}
		$wc = WC();
		if ( ! $wc || ! isset( $wc->cart ) || ! is_object( $wc->cart ) ) {
			return 0.0;
		}
		return (float) $wc->cart->get_total( 'edit' );
	}

	private static function wooProductInCart( int $product_id ): bool {
		if ( $product_id <= 0 || ! function_exists( 'WC' ) ) {
			return false;
		}
		$wc = WC();
		if ( ! $wc || ! isset( $wc->cart ) || ! is_object( $wc->cart ) ) {
			return false;
		}
		foreach ( $wc->cart->get_cart() as $item ) {
			$pid = isset( $item['product_id'] ) ? (int) $item['product_id'] : 0;
			$vid = isset( $item['variation_id'] ) ? (int) $item['variation_id'] : 0;
			if ( $pid === $product_id || $vid === $product_id ) {
				return true;
			}
		}
		return false;
	}

	private static function isEddPage( string $page ): bool {
		switch ( $page ) {
			case 'checkout':
				return function_exists( 'edd_is_checkout' ) && edd_is_checkout();
			case 'success':
				return function_exists( 'edd_is_success_page' ) && edd_is_success_page();
			case 'purchase_history':
				return function_exists( 'edd_is_purchase_history_page' ) && edd_is_purchase_history_page();
			case 'download':
				return is_singular( 'download' );
			default:
				return false;
		}
	}

	private static function isMemberPressPage( string $page ): bool {
		if ( ! defined( 'MEPR_VERSION' ) ) {
			return false;
		}
		$uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		switch ( $page ) {
			case 'login':
				return function_exists( 'is_account_page' ) ? false : (bool) preg_match( '/login/i', $uri );
			case 'account':
				return (bool) preg_match( '/account/i', $uri );
			case 'register':
				return (bool) preg_match( '/register|signup|membership/i', $uri );
			case 'thankyou':
				return (bool) preg_match( '/thank-?you/i', $uri );
			default:
				return false;
		}
	}

	private static function memberPressStatus(): string {
		if ( ! class_exists( '\MeprUser' ) || ! is_user_logged_in() ) {
			return 'none';
		}
		try {
			$user = new \MeprUser( get_current_user_id() );
			if ( method_exists( $user, 'is_active' ) && $user->is_active() ) {
				return 'active';
			}
			if ( method_exists( $user, 'is_expired' ) && $user->is_expired() ) {
				return 'expired';
			}
		} catch ( \Throwable $e ) {
			return 'none';
		}
		return 'pending';
	}
}
