<?php
/**
 * BricksExtras Favorite
 *
 * Handles favorite/wishlist functionality for posts.
 *
 * @package BricksExtras
 */

namespace BricksExtras;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Favorite class.
 *
 * Manages favorite posts functionality including cookies, user meta, and AJAX handlers.
 */
class Favorite {

	/**
	 * Whether to use post meta functionality.
	 *
	 * @var bool
	 */
	private $use_post_meta = true;

	/**
	 * Initialize the favorite functionality.
	 *
	 * Sets up hooks and filters for favorite post management.
	 */
	public function init() {

		// Allow disabling post meta functionality via filter.
		$this->use_post_meta = apply_filters( 'bricksextras/favorite_post_meta', true );

		add_action( 'wp', array( $this, 'x_update_session_cookie' ) );

		/* add attribues on loop element when using favorites loop. */
		add_filter( 'bricks/element/render_attributes', array( $this, 'favorite_loop_attributes' ), 10, 3 );

		/* when using favorites button. */
		add_action( 'wp_ajax_x_update_favorite', array( $this, 'x_update_favorite' ) );

		/* ajax remove favorites data if cookies disallowed. */
		add_action( 'wp_ajax_nopriv_x_remove_favorite', array( $this, 'x_remove_favorite' ) );

		/* delete session cookie on log out. */
		add_action( 'wp_logout', array( $this, 'x_delete_session_cookies' ) );

		/* set session cookie (and save to user meta if not existing) on log in. */
		add_action( 'wp_login', array( $this, 'x_on_user_login' ), 10, 2 );

		/* set login cookie only if users logged in. */
		add_action( 'template_redirect', array( $this, 'set_logged_in_cookie' ) );

		$published_syncing = apply_filters( 'bricksextras/favorites/published_syncing', true );

		if ( $published_syncing ) {

			/* when post deleted or trashed, remove from users lists. */
			add_action( 'delete_post', array( $this, 'x_cleanup_user_meta_on_post_change' ), 10, 2 );
			add_action( 'wp_trash_post', array( $this, 'x_cleanup_user_meta_on_post_change' ), 10, 2 );

			/* when post no longer published, remove from user's lists. */
			add_action(
				'transition_post_status',
				function ( $new_status, $old_status, $post ) {
					// Only proceed if the old status is 'publish' AND the new status is not 'publish'.
					if ( 'publish' === $old_status && 'publish' !== $new_status ) {
						self::x_cleanup_user_meta_on_post_change( $post->ID, $post );
					}
				},
				10,
				3
			);

		}
	}




	/**
	 * Adds favorite item attributes to the element based on query settings.
	 *
	 * @param array  $attributes The current attributes for the element.
	 * @param string $key The key of the current element.
	 * @param object $element The element object being processed.
	 * @return array The modified attributes array with additional favorite item data.
	 */
	public function favorite_loop_attributes( $attributes, $key, $element ): array {

		if ( method_exists( '\Bricks\Query', 'get_loop_object' ) && method_exists( '\Bricks\Query', 'is_looping' ) && \Bricks\Query::is_looping() ) {

			$query_obj = \Bricks\Query::get_loop_object();

			if ( is_object( $query_obj ) && isset( $element->settings['query']['objectType'] ) && 'queryLoopExtras' === $element->settings['query']['objectType'] ) {

				if ( isset( $element->settings['extrasQuery'] ) && 'favorite' === $element->settings['extrasQuery'] ) {

					$post_type = isset( $element->settings['post_type'] ) ? esc_attr( $element->settings['post_type'] ) : 'post';

					if ( 'any' === $post_type ) {
						$post_type = 'custom';
					}

					$post_type_name = isset( $element->settings['post_type'] ) ? $post_type : 'post';
					$list_slug      = isset( $element->settings['listSlug'] ) ? esc_attr( $element->settings['listSlug'] ) : false;

					$favorite_item = (bool) $list_slug ? $post_type_name . '__' . $list_slug : $post_type_name;

					$attributes['_root']['data-x-favorite-item'] = esc_attr( $favorite_item );
				}
			}
		}

		return $attributes;
	}


	/**
	 * Updates session cookie for logged-in users.
	 *
	 * @return void This function does not return a value.
	 */
	public function x_update_session_cookie() {

		if ( ! is_user_logged_in() ) {
			return; // Exit early if the user is not logged in.
		}

		global $x_favorite_data_global; // Declare global variable for favorite data.

		if ( is_admin() || \BricksExtras\Helpers::maybe_preview() ) {
			return; // Exit if in the admin area.
		}

		$meta_name     = 'x_favorite_ids';
		$user_id       = get_current_user_id();
		$transient_key = 'x_favorite_ids_transient_' . get_current_user_id(); // unique key for each user.

		$favorite_data = array();

		// Check for a transient storing the favorite data.
		$favorite_data_transient = get_transient( $transient_key );

		// Only fetch from user meta and update the transient if no existing transient is found.
		if ( false === $favorite_data_transient ) {

			// Get user meta.
			$existing_favorite_json = get_user_meta( $user_id, $meta_name, true );

			// If user meta exists, decode it.
			if ( $existing_favorite_json ) {
				$favorite_data = json_decode( $existing_favorite_json, true );

				if ( json_last_error() === JSON_ERROR_NONE && is_array( $favorite_data ) ) {
					// Ensure all IDs are integers.
					$favorite_data = \BricksExtras\Helpers::array_values_from_meta_as_integers( $favorite_data );

					// Set a 12hr transient for caching.
					set_transient( $transient_key, wp_json_encode( $favorite_data ), 12 * HOUR_IN_SECONDS );
				} else {
					// Log JSON error if needed (optional).
					$favorite_data = array(); // Clear on JSON error.
				}
			} else {
				$favorite_data = array(); // Default to an empty array if no meta found.
			}
		} elseif ( \BricksExtras\Helpers::is_json( $favorite_data_transient ) ) {

				$decoded_data = json_decode( $favorite_data_transient, true );

			if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded_data ) ) {
				$favorite_data = \BricksExtras\Helpers::array_values_from_meta_as_integers( $decoded_data );
			}
		} else {
				$favorite_data = array();
		}

		$x_favorite_data_global = $favorite_data; // Set global variable with favorite data.

		// If there's valid favorite data, set or update the cookie.
		if ( ! empty( $favorite_data ) ) {
			foreach ( $favorite_data as $post_type => $ids ) {
				self::set_favorite_cookie( $post_type, $ids );
			}
		} else {
			// Clear cookie if no favorite data is available.
			foreach ( $_COOKIE as $name => $value ) {
				$name = sanitize_text_field( $name );
				if ( strpos( $name, 'x_favorite_ids__' ) === 0 ) {
					setcookie( $name, '', time() - 3600, '/' );
					unset( $_COOKIE[ $name ] );
				}
			}
		}
	}



	/**
	 * Handles AJAX request to remove favorite data when cookies are disallowed.
	 *
	 * @return void Sends a JSON success response and terminates the script.
	 */
	public function x_remove_favorite() {

		check_ajax_referer( 'favorite_nonce', 'sec' );

		// Loop through all cookies and remove those that start with 'x_favorite_ids__'.
		foreach ( $_COOKIE as $key => $value ) {

			$key = sanitize_text_field( $key );

			if ( strpos( $key, 'x_favorite_' ) === 0 ) {
				setcookie( $key, '', time() - 3600, '/' ); // Expire the cookie.
				unset( $_COOKIE[ $key ] ); // Remove from $_COOKIE superglobal.
			}
		}

		wp_send_json_success(); // Send a success response.

		wp_die(); // Terminate script.
	}

	/**
	 * Helper: Get favorite cookies.
	 *
	 * @return array Array of favorite cookies
	 */
	private static function get_favorite_cookies() {

		$cookie_prefix    = 'x_favorite_ids__';
		$max_lists        = apply_filters( 'bricksextras/favorites/max_lists', 25 );
		$favorite_cookies = array();
		$lists_processed  = 0;

		foreach ( array_keys( $_COOKIE ) as $key ) {
			$key = sanitize_text_field( $key );

			if ( strpos( $key, $cookie_prefix ) === 0 ) {
				if ( $lists_processed >= $max_lists ) {
					break;
				}

				$favorite_cookies[ $key ] = \BricksExtras\Helpers::get_cookie_value( $key );
				++$lists_processed;
			}
		}

		return $favorite_cookies;
	}

	/**
	 * Set favorite cookie.
	 *
	 * @param string $post_type The post type key for the cookie.
	 * @param array  $post_ids Array of post IDs to store.
	 * @param array  $cookie_options Optional cookie options (expires, path, domain, etc.).
	 * @return void
	 */
	private static function set_favorite_cookie( $post_type, $post_ids, $cookie_options = array() ) {

		$cookie_name      = 'x_favorite_ids__' . sanitize_key( $post_type );
		$new_cookie_value = wp_json_encode( $post_ids );

		// Default cookie options.
		$defaults = array(
			'path'     => COOKIEPATH ? COOKIEPATH : '/',
			'domain'   => COOKIE_DOMAIN,
			'secure'   => is_ssl(),
			'samesite' => 'Strict',
		);

		$options = wp_parse_args( $cookie_options, $defaults );

		// Only set the cookie if headers haven't been sent yet.
		if ( ! headers_sent() ) {
			setcookie( $cookie_name, $new_cookie_value, $options );
		}

		// Make cookie available in the current request.
		$_COOKIE[ $cookie_name ] = $new_cookie_value;
	}

	/**
	 * Checks if favorites need syncing and triggers it for logged-out users.
	 *
	 * @return void
	 */
	public static function check_and_sync_favorites() {

		static $already_checked = false;

		if ( $already_checked ) {
			return;
		}

		$already_checked = true;

		if ( is_user_logged_in() || is_admin() || \BricksExtras\Helpers::maybe_preview() ) {
			return;
		}

		$favorite_cookies = self::get_favorite_cookies();

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

		$last_change = intval( get_transient( 'x_favorites_last_change' ) );

		if ( ! $last_change ) {
			return;
		}

		$last_synced = isset( $_COOKIE['x_favorite_time'] ) ? intval( $_COOKIE['x_favorite_time'] ) : 0;

		if ( $last_change > $last_synced ) {
			self::x_validate_favorite_ids();
		}
	}


	/**
	 * Validates and processes favorite IDs stored in cookies for logged-out users.
	 *
	 * This function checks the cookies for any favorite IDs that start with x_favorite_ids__.
	 * It decodes the JSON data from the cookies, validates the post types, and updates
	 * the cookies to only contain IDs for posts that are still published.
	 *
	 * @return void
	 */
	private static function x_validate_favorite_ids() {

		$favorite_data    = array();
		$favorite_cookies = self::get_favorite_cookies();

		// Loop through favorite cookies.
		foreach ( $favorite_cookies as $key => $value ) {

			$cookie_value = wp_unslash( $value );

			if ( \BricksExtras\Helpers::is_json( $cookie_value ) ) {

				$decoded_data = json_decode( $cookie_value, true );

				if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded_data ) ) {

					$decoded_data  = \BricksExtras\Helpers::array_values_as_integers( $decoded_data );
					$max_favorites = apply_filters( 'bricksextras/favorites/max_items', 1000 );
					$decoded_data  = array_slice( $decoded_data, 0, $max_favorites );

					if ( ! empty( $decoded_data ) ) {
						$list_parts     = explode( '__', $key );
						$base_post_type = $list_parts[1];

						if ( post_type_exists( $base_post_type ) ) {
							if ( ! isset( $favorite_data[ $key ] ) ) {
								$favorite_data[ $key ] = array();
							}
							$favorite_data[ $key ] = array_unique( array_merge( $favorite_data[ $key ], $decoded_data ) );
						}
					}
				}
			}
		}

		$expire_days = apply_filters( 'bricksextras/favorite/expiry', 7 );
		$expires     = (int) ( time() + ( floatval( $expire_days ) * 24 * 60 * 60 ) );

		$favorite_data = self::update_cookie_for_count( $favorite_data, $expires, false );

		if ( isset( $_COOKIE['x_favorite_allow'] ) && 'true' === $_COOKIE['x_favorite_allow'] ) {
			setcookie(
				'x_favorite_time',
				(string) time(),
				array(
					'expires'  => $expires,
					'path'     => COOKIEPATH ? COOKIEPATH : '/',
					'domain'   => COOKIE_DOMAIN,
					'secure'   => is_ssl(),
					'samesite' => 'Strict',
				)
			);
		}
	}



	/**
	 * Handles AJAX request to update favorite items.
	 *
	 * @return void Sends a JSON success or error response and terminates the script.
	 */
	public function x_update_favorite() {

		check_ajax_referer( 'favorite_nonce', 'sec' );

		if ( is_user_logged_in() && isset( $_POST['post_id'], $_POST['post_type'], $_POST['type'], $_POST['listMaximum'] ) ) {

			// Sanitize all POST variables.
			$post_id      = absint( Helpers::get_post_value( 'post_id' ) );
			$post_type    = sanitize_key( Helpers::get_post_value( 'post_type' ) );
			$type         = Helpers::get_post_value( 'type' );
			$cookie_name  = 'x_favorite_ids__' . $post_type;
			$max_items    = apply_filters( 'bricksextras/favorites/max_items', 1000 );
			$list_maximum = min( absint( Helpers::get_post_value( 'listMaximum' ) ), $max_items );

			$meta_name = 'x_favorite_ids';

			$maximum_reached = false;
			$set_maximum     = ( $list_maximum >= 1 ); // Determine if a maximum limit is set.

			$user_id = get_current_user_id();

			// Retrieve existing favorite data from user meta.
			$existing_favorite_json = get_user_meta( $user_id, $meta_name, true );
			$favorite_data          = json_decode( $existing_favorite_json, true );

			if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $favorite_data ) ) {
				$favorite_data = array(); // Initialize empty array if JSON is invalid.
			} else {
				$favorite_data = \BricksExtras\Helpers::array_values_from_meta_as_integers( $favorite_data );
			}

			// Initialize post type array if not set.
			if ( ! isset( $favorite_data[ $post_type ] ) ) {
				$max_lists = apply_filters( 'bricksextras/favorites/max_lists', 25 );
				if ( count( $favorite_data ) >= $max_lists ) {
					wp_die();
				}
				$favorite_data[ $post_type ] = array();
			}

			if ( 'clear' === $type ) {
				// Check if this is a non-custom list (doesn't contain double underscore).
				if ( strpos( $post_type, '__' ) === false && isset( $favorite_data[ $post_type ] ) ) {
					// Get all post IDs that were in the list before clearing.
					$post_ids_to_clear = $favorite_data[ $post_type ];

					// Remove user ID from post meta for each post in the list.
					if ( $this->use_post_meta ) {
						foreach ( $post_ids_to_clear as $pid ) {
							$post_meta_key  = 'x_favorite_users';
							$favorite_users = get_post_meta( $pid, $post_meta_key, true );

							if ( ! empty( $favorite_users ) ) {
								$favorite_users = json_decode( $favorite_users, true );
								if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $favorite_users ) ) {
									$favorite_users = array();
								}

								// Remove the user ID if it exists in the array.
								$key = array_search( $user_id, $favorite_users, true );
								if ( false !== $key ) {
									unset( $favorite_users[ $key ] );
									$favorite_users = array_values( $favorite_users ); // Re-index array.
									update_post_meta( $pid, $post_meta_key, wp_json_encode( $favorite_users ) );

									// Update the count meta.
									update_post_meta( $pid, 'x_favorite_users_count', count( $favorite_users ) );
								}
							}
						}
					}
				}

				// Clear the data.
				if ( isset( $favorite_data[ $post_type ] ) ) {
					$favorite_data[ $post_type ] = array(); // Reset the array if it exists.
				}
			} else {

				// Check if the post ID already exists in the array.
				if ( in_array( $post_id, $favorite_data[ $post_type ], true ) || 'remove' === $type ) {
					// Remove the post ID from favorites.
					$favorite_data[ $post_type ] = array_values( array_diff( $favorite_data[ $post_type ], array( $post_id ) ) );

					if ( $this->use_post_meta ) {
						// Check if this is a non-custom list (doesn't contain double underscore).
						if ( strpos( $post_type, '__' ) === false ) {
							// Remove user ID from post meta.
							$post_meta_key  = 'x_favorite_users';
							$favorite_users = get_post_meta( $post_id, $post_meta_key, true );

							if ( ! empty( $favorite_users ) ) {
								$favorite_users = json_decode( $favorite_users, true );
								if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $favorite_users ) ) {
									$favorite_users = array();
								}

								// Remove the user ID if it exists in the array.
								$key = array_search( $user_id, $favorite_users, true );
								if ( false !== $key ) {
									unset( $favorite_users[ $key ] );
									$favorite_users = array_values( $favorite_users ); // Re-index array.
									update_post_meta( $post_id, $post_meta_key, wp_json_encode( $favorite_users ) );

									// Update the count meta.
									update_post_meta( $post_id, 'x_favorite_users_count', count( $favorite_users ) );
								}
							}
						}
					}
				} else {
					// Check if we can add the post ID.
					$effective_max = $set_maximum ? min( $list_maximum, $max_items ) : $max_items;
					if ( count( $favorite_data[ $post_type ] ) < $effective_max ) {
						$favorite_data[ $post_type ][] = $post_id; // Add post ID to favorites.

						// Check if this is a non-custom list (doesn't contain double underscore).
						if ( strpos( $post_type, '__' ) === false && $this->use_post_meta ) {
							// Add user ID to post meta.
							$post_meta_key  = 'x_favorite_users';
							$favorite_users = get_post_meta( $post_id, $post_meta_key, true );

							if ( empty( $favorite_users ) ) {
								// If no users have favorited this post yet, create new array with this user.
								$favorite_users      = array( $user_id );
								$favorite_users_json = wp_json_encode( $favorite_users );
								update_post_meta( $post_id, $post_meta_key, $favorite_users_json );

								// Initialize the count meta.
								update_post_meta( $post_id, 'x_favorite_users_count', 1 );
							} else {
								// Decode existing favorite users.
								$favorite_users = json_decode( $favorite_users, true );
								if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $favorite_users ) ) {
									$favorite_users = array();
								}

								// Add user ID if not already in the array.
								if ( ! in_array( $user_id, $favorite_users, true ) ) {
									$favorite_users[] = $user_id;
									$favorite_users   = array_values( array_unique( $favorite_users ) );
									update_post_meta( $post_id, $post_meta_key, wp_json_encode( $favorite_users ) );

									// Update the count meta.
									update_post_meta( $post_id, 'x_favorite_users_count', count( $favorite_users ) );
								}
							}
						}
					} else {
						$maximum_reached = true; // Mark that the maximum has been reached.
					}
				}
			}

			// Save the data after change.
			$new_meta_value = wp_json_encode( $favorite_data );

			// Save the data after change.
			$new_cookie_value = wp_json_encode( $favorite_data[ $post_type ] );

			$transient_key = 'x_favorite_ids_transient_' . get_current_user_id(); // unique key for each user.

			// Update user meta.
			update_user_meta( $user_id, $meta_name, $new_meta_value );
			// Set a 12hr transient for caching.
			set_transient( $transient_key, wp_json_encode( $favorite_data ), 12 * HOUR_IN_SECONDS );

			// Update the session cookie.
			setcookie(
				$cookie_name,
				$new_cookie_value,
				array(
					'path'     => COOKIEPATH ? COOKIEPATH : '/',
					'domain'   => COOKIE_DOMAIN,
					'secure'   => is_ssl(),
					'samesite' => 'Strict',
				)
			);

			$_COOKIE[ $cookie_name ] = $new_cookie_value;

			// Send the success response.
			wp_send_json_success(
				array(
					'data'    => $favorite_data,
					'message' => $maximum_reached,
				)
			);
		} else {
			wp_send_json_error( array( 'message' => 'Invalid input data' ) );
		}

		wp_die();
	}


	/**
	 * Deletes session cookies related to user's favorite items upon logout.
	 *
	 * This function clears all cookies that start with the prefix 'x_favorite_ids__'.
	 * It sets each cookie's expiration time to the past to effectively delete them.
	 *
	 * @return void
	 */
	public function x_delete_session_cookies() {

		// Clear all cookies starting with 'x_favorite_ids__'.
		foreach ( $_COOKIE as $name => $value ) {

			$name = sanitize_text_field( $name );

			if ( strpos( $name, 'x_favorite_' ) === 0 ) {
				setcookie( $name, '', time() - 3600, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN );
				unset( $_COOKIE[ $name ] );
			}
		}
	}


	/**
	 * Handles user login and syncs favorite items from cookies to user meta.
	 *
	 * @param string   $user_login The username of the logging-in user.
	 * @param \WP_User $user The WP_User object for the logging-in user.
	 * @return void
	 */
	public function x_on_user_login( $user_login, $user ) {

		$user_id       = $user->ID;
		$meta_name     = 'x_favorite_ids';
		$favorite_data = array();

		// Retrieve existing favorite data from user meta.
		$existing_favorite_json = get_user_meta( $user_id, $meta_name, true );

		delete_transient( 'x_favorite_ids_transient_' . $user_id );
		wp_cache_delete( 'x_favorite_ids_transient_' . $user_id, 'transient' );

		// Check if user meta already exists.
		if ( empty( $existing_favorite_json ) ) {

			$favorite_cookies = self::get_favorite_cookies();

			// Iterating through cookies to preserve post_type keys in favorite_data.
			foreach ( $favorite_cookies as $cookie_name => $cookie_value ) {

				if ( ! \BricksExtras\Helpers::is_json( $cookie_value ) ) {
					continue;
				}

				// Decode cookie data.
				$cookie_data = json_decode( $cookie_value, true );

				// Check if the JSON is valid.
				if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $cookie_data ) ) {
					continue;
				}

				// Extract post type from the cookie name (e.g., x_favorite_ids__post becomes post).
				$post_type_key = sanitize_key( str_replace( 'x_favorite_ids__', '', $cookie_name ) );

				// Merge IDs if already present, otherwise set directly.
				$favorite_data[ $post_type_key ] = isset( $favorite_data[ $post_type_key ] )
					? array_merge( $favorite_data[ $post_type_key ], $cookie_data )
					: $cookie_data;
			}

			// Validate and filter the collected favorite data.
			if ( ! empty( $favorite_data ) ) {

				foreach ( $favorite_data as $post_type_key => $post_ids ) {
					$favorite_data[ $post_type_key ] = \BricksExtras\Helpers::array_values_as_integers( $post_ids );
				}

				$published_syncing = apply_filters( 'bricksextras/favorites/published_syncing', true );

				if ( $published_syncing ) {
					$post_ids_to_check = array();
					$posts_to_filter   = array();

					$registered_post_types = get_post_types( array( 'public' => true ), 'names' );

					$key = array_search( 'bricks_template', $registered_post_types, true );
					if ( false !== $key ) {
						unset( $registered_post_types[ $key ] );
					}

					// Collect post IDs across post types.
					foreach ( $favorite_data as $post_type_key => $post_ids ) {
						if ( in_array( $post_type_key, $registered_post_types, true ) ) {
							$posts_to_filter[ $post_type_key ] = $post_ids;
							$post_ids_to_check                 = array_merge( $post_ids_to_check, $post_ids );
						}
					}

					// Get only published posts.
					$published_posts = get_posts(
						array(
							'post_type'   => array( 'any' ),
							'post__in'    => $post_ids_to_check,
							'post_status' => array( 'publish', 'inherit' ),
							'numberposts' => -1,
							'fields'      => 'ids',
						)
					);

					// Make published post IDs a simple array.
					$published_post_ids = array_values( $published_posts );

					// Filter favorite data based on published posts.
					foreach ( $posts_to_filter as $post_type_key => $post_ids ) {
						// Use in_array to check if the post ID exists in published_post_ids.
						$filtered_ids = array_filter(
							$post_ids,
							function ( $post_id ) use ( $published_post_ids ) {
								return in_array( $post_id, $published_post_ids, true ); // Check existence correctly.
							}
						);

						// Update favorite_data with filtered IDs.
						if ( ! empty( $filtered_ids ) ) {
							$favorite_data[ $post_type_key ] = array_values( $filtered_ids );
						} else {
							unset( $favorite_data[ $post_type_key ] ); // Remove empty keys.
						}
					}
				}

				// Update user meta if there's valid favorite data.
				if ( ! empty( $favorite_data ) ) {
					$new_meta_value = wp_json_encode( $favorite_data );
					update_user_meta( $user_id, $meta_name, $new_meta_value );

					if ( $this->use_post_meta ) {

						// Update post meta for each post in non-custom lists.
						foreach ( $favorite_data as $post_type_key => $post_ids ) {
							// Only update post meta for non-custom lists (no double underscore).
							if ( strpos( $post_type_key, '__' ) === false ) {
								foreach ( $post_ids as $post_id ) {
									// Get existing favorite users for this post.
									$post_meta_key  = 'x_favorite_users';
									$favorite_users = get_post_meta( $post_id, $post_meta_key, true );

									if ( empty( $favorite_users ) ) {
										// If no users have favorited this post yet, create new array with this user.
										$favorite_users = array( $user_id );
										update_post_meta( $post_id, $post_meta_key, wp_json_encode( $favorite_users ) );

										// Initialize the count meta.
										update_post_meta( $post_id, 'x_favorite_users_count', 1 );
									} else {
										// Decode existing favorite users.
										$favorite_users = json_decode( $favorite_users, true );
										if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $favorite_users ) ) {
											$favorite_users = array();
										}

										// Add user ID if not already in the array.
										if ( ! in_array( $user_id, $favorite_users, true ) ) {
											$favorite_users[] = $user_id;
											$favorite_users   = array_values( array_unique( $favorite_users ) );
											update_post_meta( $post_id, $post_meta_key, wp_json_encode( $favorite_users ) );

											// Update the count meta.
											update_post_meta( $post_id, 'x_favorite_users_count', count( $favorite_users ) );
										}
									}
								}
							}
						}
					}
				}
			}
		}

		// Handle existing user meta.
		if ( ! empty( $existing_favorite_json ) ) {
			$favorite_data = json_decode( $existing_favorite_json, true );

			if ( json_last_error() !== JSON_ERROR_NONE ) {
				$favorite_data = false;
			} else {
				$favorite_data = \BricksExtras\Helpers::array_values_from_meta_as_integers( $favorite_data );
			}
		}

		// Set session cookie based on favorite data.
		if ( $favorite_data && is_array( $favorite_data ) ) {

			// Clear existing cookies.
			foreach ( $_COOKIE as $cookie_name => $value ) {

				$cookie_name = sanitize_text_field( $cookie_name );

				if ( strpos( $cookie_name, 'x_favorite_ids__' ) === 0 ) {
					setcookie( $cookie_name, '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN );
					unset( $_COOKIE[ $cookie_name ] );
				}
			}

			// Set the new cookies.
			foreach ( $favorite_data as $post_type => $post_ids ) {
				self::set_favorite_cookie( $post_type, $post_ids );
			}
		}
	}




	/**
	 * Sets a logged-in cookie to indicate the user is authenticated.
	 *
	 * @return void
	 */
	public function set_logged_in_cookie() {

		// Only proceed if headers haven't been sent yet.
		if ( headers_sent() ) {
			return;
		}

		if ( is_user_logged_in() ) {
			setcookie( 'x_logged_in_user', '1', 0, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), false );
		} elseif ( isset( $_COOKIE['x_logged_in_user'] ) ) {

				setcookie( 'x_logged_in_user', '', time() - 3600, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), false );
		}
	}


	/**
	 * Cleans up user meta when a post is deleted, trashed, or unpublished.
	 *
	 * @param int      $post_id The post ID.
	 * @param \WP_Post $post The post object.
	 * @return void This function does not return a value. It updates user meta directly.
	 */
	public static function x_cleanup_user_meta_on_post_change( $post_id, $post ) {

		$meta_name = 'x_favorite_ids';

		$post_type = get_post_type( $post_id ); // Get post type of the deleted post.

		$excluded_post_types = array( 'revision', 'nav_menu_item', 'custom_css', 'oembed_cache', 'user_request', 'wp_block', 'bricks_template' );

		$excluded_post_types = apply_filters( 'bricksextras/favorites/excluded_post_types', $excluded_post_types );

		// If the post type is in the excluded list, return early.
		if ( in_array( $post_type, $excluded_post_types, true ) ) {
			return;
		}

		set_transient( 'x_favorites_last_change', time(), 0 );

		// Only get users who have the `x_favorite_ids` meta key set.
		$user_ids = get_users(
			array(
				'meta_key' => $meta_name,
				'fields'   => 'ID',
			)
		);

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

		foreach ( $user_ids as $user_id ) {
			// Get user meta.
			$existing_favorite_json = get_user_meta( $user_id, $meta_name, true );

			if ( ! $existing_favorite_json || empty( $existing_favorite_json ) ) {
				continue;
			}

			// Decode the JSON data.
			$favorite_data = json_decode( $existing_favorite_json, true );

			if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $favorite_data ) ) {
				continue;
			}

			$data_changed = false;

			// Loop through each post type or custom key.
			foreach ( $favorite_data as $post_type_key => &$post_ids ) {

				// Check if the key matches the post type, directly or with a `__` suffix.
				if ( $post_type_key === $post_type || strpos( $post_type_key, "{$post_type}__" ) === 0 || strpos( $post_type_key, 'custom__' ) === 0 ) {

					// Check if the post ID exists in the array and remove it.
					$key = array_search( $post_id, $post_ids, true );
					if ( false !== $key ) {
						unset( $post_ids[ $key ] );
						$post_ids     = array_values( $post_ids ); // Re-index array.
						$data_changed = true;
					}

					// Remove empty keys.
					if ( empty( $post_ids ) ) {
						unset( $favorite_data[ $post_type_key ] );
					}
				}
			}
			unset( $post_ids ); // Clear reference.

			// Update meta only if changes were made.
			if ( $data_changed ) {

				$new_json_value = wp_json_encode( $favorite_data );

				if ( $new_json_value !== $existing_favorite_json ) {
					update_user_meta( $user_id, $meta_name, $new_json_value );
					delete_transient( 'x_favorite_ids_transient_' . $user_id );
				}
			}
		}
	}



	/**
	 * Update cookies for the count of favorite posts based on their publish status.
	 *
	 * @param array        $favorite_data An associative array of favorite IDs categorized by post type.
	 * @param int          $expires The expiration time for the cookies.
	 * @param string|false $list_name (Optional) The specific list name to filter favorites by.
	 * @return array The filtered favorite data, containing only published post IDs.
	 */
	private static function update_cookie_for_count( $favorite_data, $expires, $list_name = false ): array {

		$filtered_favorite_data = array();

		// Check if a specific list is known.
		if ( $list_name ) {
			// Extract the base post type from the list name.
			$base_post_type = str_replace( 'x_favorite_ids__', '', $list_name );

			// Check if the specific post type exists in the favorite data.
			if ( isset( $favorite_data[ $list_name ] ) ) {
				$post_ids_to_check = $favorite_data[ $list_name ];

				// Get only published posts for this specific post type.
				$published_posts = get_posts(
					array(
						'post_type'   => $base_post_type,
						'post__in'    => $post_ids_to_check,
						'post_status' => array( 'publish', 'inherit' ),
						'numberposts' => -1,
						'fields'      => 'ids',
					)
				);

				// Update the filtered favorite data with any published post IDs.
				if ( ! empty( $published_posts ) ) {
					$filtered_favorite_data[ $list_name ] = array_values( array_intersect( $post_ids_to_check, $published_posts ) );
				} else {
					$filtered_favorite_data[ $list_name ] = array();
				}
			}
		} else { // Check all lists if no specific list is provided.

			// Loop through each key in the favorite data.
			foreach ( $favorite_data as $post_type_key => $post_ids ) {
				// Check if this key corresponds to a post type.
				$list_parts     = explode( '__', $post_type_key );
				$base_post_type = $list_parts[1]; // Get base post type.

				// Only check against published posts if the post type exists.
				if ( post_type_exists( $base_post_type ) ) {
					// Get only published posts for this post type.
					$published_posts = get_posts(
						array(
							'post_type'   => $base_post_type,
							'post__in'    => $post_ids,
							'post_status' => array( 'publish', 'inherit' ),
							'numberposts' => -1,
							'fields'      => 'ids',
						)
					);

					// Update the filtered favorite data with only published post IDs.
					if ( ! empty( $published_posts ) ) {
						$filtered_favorite_data[ $post_type_key ] = array_values( array_intersect( $post_ids, $published_posts ) );
					} else {
						$filtered_favorite_data[ $post_type_key ] = array();
					}
				}
			}
		}

		// Set cookies based on filtered favorite data.
		foreach ( $filtered_favorite_data as $post_type => $ids ) {

			// If there are valid IDs for this post type.
			if ( ! empty( $ids ) ) {

				$new_cookie_value = wp_json_encode( array_values( array_unique( $ids ) ) );

				// Clear existing cookie.
				setcookie( "$post_type", '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN );
				// Set the new cookie for this post type.
				setcookie(
					"$post_type",
					$new_cookie_value,
					array(
						'expires'  => $expires,
						'path'     => COOKIEPATH ? COOKIEPATH : '/',
						'domain'   => COOKIE_DOMAIN,
						'secure'   => is_ssl(),
						'samesite' => 'Strict',
					)
				);

				// Make cookie available in the current request.
				$_COOKIE[ "$post_type" ] = $new_cookie_value;
			} else {
				// Clear the cookie if there are no valid IDs.
				setcookie( "$post_type", '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN );
			}
		}

		// Check if the 'x_favorite_allow' cookie is set and has the value 'true'.
		if ( isset( $_COOKIE['x_favorite_allow'] ) && 'true' === $_COOKIE['x_favorite_allow'] ) {

			/* reset the time. */
			setcookie(
				'x_favorite_time',
				(string) time(),
				array(
					'expires'  => $expires,
					'path'     => COOKIEPATH ? COOKIEPATH : '/',
					'domain'   => COOKIE_DOMAIN,
					'secure'   => is_ssl(),
					'samesite' => 'Strict',
				)
			);
		}

		return $filtered_favorite_data; // Return the updated data for further use if needed.
	}
}
