<?php
/**
 * Field-level visibility gating.
 *
 * Two independent, AND-combined gates per repeater field:
 *
 *   1. AUDIENCE gate (viewer-based) — who is allowed to see the field:
 *        'everyone'       — no gate (default)
 *        'logged_in'      — viewer must be authenticated
 *        'specific_roles' — viewer must hold one of the configured WP/UM roles
 *      Administrators always pass the audience gate.
 *
 *   2. VALUE condition (subject-based) — show the field only when a meta key
 *      on the *user being rendered* matches a comparison, e.g.
 *      "show the Gold badge only if membership = gold".
 *      The value condition applies to everyone (including admins) because it is
 *      data-driven, not a permission. The subject is the profile/row user, NOT
 *      the viewer.
 *
 * A field is shown only when BOTH gates pass.
 *
 * Backwards compatible: can_view($item), can_view_key($key,$map) and
 * build_visibility_map($items) keep their original signatures; the new
 * $subject_user_id argument is optional.
 *
 * @package UM_Elementor_Free
 */

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

final class UM_Elementor_Field_Visibility {

	/* -- Audience levels ----------------------------------------------------- */
	public const EVERYONE       = 'everyone';
	public const LOGGED_IN      = 'logged_in';
	public const SPECIFIC_ROLES = 'specific_roles';

	/* -- Value-condition operators ------------------------------------------- */
	public const OP_IS           = 'is';
	public const OP_IS_NOT       = 'is_not';
	public const OP_CONTAINS     = 'contains';
	public const OP_NOT_CONTAINS = 'not_contains';
	public const OP_IN_LIST      = 'in_list';
	public const OP_GREATER_THAN = 'greater_than';
	public const OP_LESS_THAN    = 'less_than';
	public const OP_EMPTY        = 'is_empty';
	public const OP_NOT_EMPTY    = 'is_not_empty';

	/**
	 * Allowed value-condition operators.
	 *
	 * @return string[]
	 */
	public static function value_operators(): array {
		return [
			self::OP_IS,
			self::OP_IS_NOT,
			self::OP_CONTAINS,
			self::OP_NOT_CONTAINS,
			self::OP_IN_LIST,
			self::OP_GREATER_THAN,
			self::OP_LESS_THAN,
			self::OP_EMPTY,
			self::OP_NOT_EMPTY,
		];
	}

	/**
	 * Evaluate one repeater item against the current viewer and (for value
	 * conditions) the subject user being rendered.
	 *
	 * @param array    $field_item      Single repeater item array from Elementor settings.
	 * @param int|null $subject_user_id The user the field belongs to (profile/directory row).
	 *                                  Null → resolved from um_user('ID') when available.
	 * @return bool True = show the field, false = hide it.
	 */
	public static function can_view( array $field_item, ?int $subject_user_id = null ): bool {
		if ( ! self::passes_audience_gate( $field_item ) ) {
			return false;
		}
		if ( ! self::passes_value_condition( $field_item, $subject_user_id ) ) {
			return false;
		}
		return true;
	}

	/**
	 * Evaluate visibility from a flat rules map (used in AJAX/table responses).
	 *
	 * @param string   $meta_key        The field meta key being evaluated.
	 * @param array    $visibility_map  Keyed by meta_key (see build_visibility_map()).
	 * @param int|null $subject_user_id The row/profile user. Null on the column-selection
	 *                                  pass, where value conditions fail open by design.
	 * @return bool
	 */
	public static function can_view_key( string $meta_key, array $visibility_map, ?int $subject_user_id = null ): bool {
		if ( ! isset( $visibility_map[ $meta_key ] ) ) {
			return true;
		}

		$entry = $visibility_map[ $meta_key ];

		$item = [
			'field_visibility'       => $entry['visibility'] ?? self::EVERYONE,
			'field_visibility_roles' => $entry['roles'] ?? [],
		];

		if ( ! empty( $entry['value_condition'] ) && is_array( $entry['value_condition'] ) ) {
			$vc = $entry['value_condition'];
			$item['field_value_condition_enable']   = 'yes';
			$item['field_value_condition_key']      = $vc['key'] ?? '';
			$item['field_value_condition_operator'] = $vc['operator'] ?? self::OP_IS;
			$item['field_value_condition_value']    = $vc['value'] ?? '';
		}

		return self::can_view( $item, $subject_user_id );
	}

	/**
	 * Build a flat map from the widget's repeater items for passing to AJAX/JS.
	 *
	 * An entry is emitted when EITHER the audience gate is non-trivial OR a value
	 * condition is enabled — so "everyone, but only if membership = gold" survives.
	 *
	 * @param array $repeater_items Value of the Elementor repeater control.
	 * @return array<string, array{visibility:string, roles:string[], value_condition: array|null}>
	 */
	public static function build_visibility_map( array $repeater_items ): array {
		$map = [];

		foreach ( $repeater_items as $item ) {
			$key = sanitize_key( $item['um_user_meta_field_value'] ?? '' );
			if ( '' === $key ) {
				continue;
			}

			$visibility = $item['field_visibility'] ?? self::EVERYONE;
			if ( ! in_array( $visibility, [ self::EVERYONE, self::LOGGED_IN, self::SPECIFIC_ROLES ], true ) ) {
				$visibility = self::EVERYONE;
			}

			$value_condition = self::extract_value_condition( $item );

			if ( self::EVERYONE === $visibility && null === $value_condition ) {
				continue;
			}

			$map[ $key ] = [
				'visibility'      => $visibility,
				'roles'           => array_map( 'sanitize_text_field', (array) ( $item['field_visibility_roles'] ?? [] ) ),
				'value_condition' => $value_condition,
			];
		}

		return $map;
	}

	private static function passes_audience_gate( array $field_item ): bool {
		$level = $field_item['field_visibility'] ?? self::EVERYONE;

		if ( ! in_array( $level, [ self::EVERYONE, self::LOGGED_IN, self::SPECIFIC_ROLES ], true ) ) {
			$level = self::EVERYONE;
		}

		if ( self::EVERYONE === $level ) {
			return true;
		}

		if ( ! is_user_logged_in() ) {
			return false;
		}

		if ( self::LOGGED_IN === $level ) {
			return true;
		}

		$user = wp_get_current_user();
		if ( $user->exists() && in_array( 'administrator', (array) $user->roles, true ) ) {
			return true;
		}

		$allowed = (array) ( $field_item['field_visibility_roles'] ?? [] );
		if ( empty( $allowed ) ) {
			return true;
		}

		return self::viewer_has_any_role( $allowed );
	}

	private static function passes_value_condition( array $field_item, ?int $subject_user_id ): bool {
		$enabled = $field_item['field_value_condition_enable'] ?? '';
		if ( ! in_array( $enabled, [ 'yes', '1', 1, true ], true ) ) {
			return true;
		}

		$key = sanitize_key( (string) ( $field_item['field_value_condition_key'] ?? '' ) );
		if ( '' === $key ) {
			return true;
		}

		$operator = (string) ( $field_item['field_value_condition_operator'] ?? self::OP_IS );
		if ( ! in_array( $operator, self::value_operators(), true ) ) {
			$operator = self::OP_IS;
		}

		$expected = (string) ( $field_item['field_value_condition_value'] ?? '' );

		$subject_id = self::resolve_subject_id( $subject_user_id );
		if ( $subject_id < 1 ) {
			return (bool) apply_filters( 'umep_field_value_condition_default_when_no_subject', true, $field_item );
		}

		$raw = get_user_meta( $subject_id, $key, true );

		return self::compare( $raw, $operator, $expected );
	}

	private static function resolve_subject_id( ?int $subject_user_id ): int {
		if ( null !== $subject_user_id && $subject_user_id > 0 ) {
			return $subject_user_id;
		}
		if ( function_exists( 'um_user' ) ) {
			$uid = (int) um_user( 'ID' );
			if ( $uid > 0 ) {
				return $uid;
			}
		}
		return 0;
	}

	private static function compare( $raw, string $operator, string $expected ): bool {
		if ( is_array( $raw ) ) {
			$values = array_map(
				static function ( $v ) {
					return is_scalar( $v ) ? (string) $v : '';
				},
				$raw
			);
		} else {
			$values = [ is_scalar( $raw ) ? (string) $raw : '' ];
		}
		$values = array_values(
			array_filter(
				$values,
				static function ( $v ) {
					return '' !== $v;
				}
			)
		);

		$haystack = implode( ', ', $values );
		$first    = $values[0] ?? '';
		$expected = trim( $expected );

		switch ( $operator ) {
			case self::OP_EMPTY:
				return [] === $values;

			case self::OP_NOT_EMPTY:
				return [] !== $values;

			case self::OP_IS:
				return in_array( $expected, $values, true );

			case self::OP_IS_NOT:
				return ! in_array( $expected, $values, true );

			case self::OP_CONTAINS:
				return '' !== $expected && false !== stripos( $haystack, $expected );

			case self::OP_NOT_CONTAINS:
				return '' === $expected || false === stripos( $haystack, $expected );

			case self::OP_IN_LIST:
				$list = array_filter(
					array_map( 'trim', explode( ',', $expected ) ),
					static function ( $v ) {
						return '' !== $v;
					}
				);
				return ! empty( array_intersect( $list, $values ) );

			case self::OP_GREATER_THAN:
				return is_numeric( $first ) && is_numeric( $expected ) && (float) $first > (float) $expected;

			case self::OP_LESS_THAN:
				return is_numeric( $first ) && is_numeric( $expected ) && (float) $first < (float) $expected;

			default:
				return true;
		}
	}

	/**
	 * @return array{key:string, operator:string, value:string}|null
	 */
	private static function extract_value_condition( array $item ): ?array {
		$enabled = $item['field_value_condition_enable'] ?? '';
		if ( ! in_array( $enabled, [ 'yes', '1', 1, true ], true ) ) {
			return null;
		}

		$key = sanitize_key( (string) ( $item['field_value_condition_key'] ?? '' ) );
		if ( '' === $key ) {
			return null;
		}

		$operator = (string) ( $item['field_value_condition_operator'] ?? self::OP_IS );
		if ( ! in_array( $operator, self::value_operators(), true ) ) {
			$operator = self::OP_IS;
		}

		return [
			'key'      => $key,
			'operator' => $operator,
			'value'    => sanitize_text_field( (string) ( $item['field_value_condition_value'] ?? '' ) ),
		];
	}

	/**
	 * @param array<string> $allowed_roles
	 */
	private static function viewer_has_any_role( array $allowed_roles ): bool {
		$user = wp_get_current_user();
		if ( ! $user->exists() ) {
			return false;
		}

		if ( ! empty( array_intersect( $allowed_roles, (array) $user->roles ) ) ) {
			return true;
		}

		if ( function_exists( 'UM' ) ) {
			$um_role = function_exists( 'um_get_user_role' )
				? um_get_user_role( $user->ID )
				: UM()->roles()->get_priority_user_role( $user->ID );

			if ( $um_role ) {
				if ( in_array( $um_role, $allowed_roles, true ) ) {
					return true;
				}
				$bare = preg_replace( '/^um_/', '', $um_role );
				if ( $bare && in_array( $bare, $allowed_roles, true ) ) {
					return true;
				}
			}
		}

		return false;
	}
}
