<?php
/**
 * BricksExtras Helpers
 *
 * Utility functions and helper methods for BricksExtras plugin.
 *
 * @package BricksExtras
 */

namespace BricksExtras;

use Bricks\Query;
use Bricks\Templates;

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

/**
 * BricksExtras Helpers Class
 *
 * Provides utility functions and helper methods for BricksExtras plugin.
 */
class Helpers {

	/**
	 * Get looping parent query Id by level.
	 *
	 * @see https://itchycode.com/bricks-builder-useful-functions-and-tips/
	 * @param int $level Nesting level (default: 1).
	 * @return mixed Query ID or false.
	 */
	public static function get_bricks_looping_parent_query_id_by_level( int $level = 1 ) {

		global $bricks_loop_query;

		if ( empty( $bricks_loop_query ) || $level < 1 ) {
			return false;
		}

		$current_query_id = Query::is_any_looping();

		if ( ! $current_query_id ) {
			return false;
		}

		if ( ! isset( $bricks_loop_query[ $current_query_id ] ) ) {
			return false;
		}

		$query_ids = array_reverse( array_keys( $bricks_loop_query ) );

		if ( ! isset( $query_ids[ $level ] ) ) {
			return false;
		}

		if ( $bricks_loop_query[ $query_ids[ $level ] ]->is_looping ) {
			return $query_ids[ $level ];
		}

		return false;
	}


	/**
	 * Check if we are viewing inside builder.
	 *
	 * @return bool True if in builder preview.
	 */
	public static function maybe_preview(): bool {

		$bricks  = self::get_get_value( 'bricks' );
		$referer = self::get_server_value( 'HTTP_REFERER' );

		$builder  = $bricks && strstr( $bricks, 'run' );
		$referrer = $referer && strstr( $referer, 'brickspreview' );

		return ( $builder || $referrer );
	}

	/**
	 * True if we're in the Gutenberg editor
	 *
	 * @return bool True if in Gutenberg editor, false otherwise
	 */
	public static function is_gutenberg_editor(): bool {
		$is_gutenberg = false;
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			$referer      = self::get_server_value( 'HTTP_REFERER' );
			$is_gutenberg = strpos( $referer, 'post.php' ) !== false ||
							strpos( $referer, 'post-new.php' ) !== false;
		}
		return $is_gutenberg;
	}


	/**
	 * Create CSS settings.
	 *
	 * @param string $property Property name.
	 * @param array  $selectors Array of CSS selectors.
	 * @param bool   $general Whether to use general selector (default: false).
	 * @return array Array of CSS rules.
	 */
	public static function do_css_rules( $property, array $selectors, $general = false ): array {

		$output = array();

		foreach ( $selectors as $selector ) {

			if ( ! $general ) {
				$selector = '.wsf-form ' . $selector;
			}

			$output[] = array(
				'property' => $property,
				'selector' => $selector,
			);
		}

		return $output;
	}

	/**
	 * Check if element CSS is already added in head.
	 *
	 * @param string $name Element name.
	 * @return bool True if CSS already added.
	 */
	public static function element_css_added( $name ) {

		global $bricks_extras_element_css_added;
		return $bricks_extras_element_css_added[ $name ] ?? true;
	}

	/**
	 * Get public custom post types.
	 *
	 * @param bool $built_in Whether to include built-in post types.
	 * @return array Array of post types.
	 */
	public static function get_public_post_types( $built_in = false ): array {
		$post_types = get_post_types(
			array(
				'public'   => true,
				'_builtin' => $built_in,
			),
			'objects'
		);
		unset( $post_types['bricks_template'] );

		$post_types_arr = array();
		foreach ( $post_types as $post_type ) {
			$post_types_arr[ $post_type->name ] = $post_type->label;
		}

		return $post_types_arr;
		// https://d.pr/i/VGYKqF.
	}



	/**
	 * Convert time string to seconds.
	 *
	 * @param string $time Time string to convert.
	 * @return int Time in seconds.
	 */
	public static function time_to_seconds( string $time ): int {

		if ( '' !== $time ) {
			if ( strpos( $time, ':' ) !== false ) {
				$arr = explode( ':', $time );
				if ( count( $arr ) === 3 ) {
					return intval( $arr[0] ) * 3600 + intval( $arr[1] ) * 60 + intval( $arr[2] );
				}
				// Handle incomplete time format (e.g., "05:" without seconds).
				if ( 2 === count( $arr ) && '' !== $arr[1] ) {
					return intval( $arr[0] ) * 60 + intval( $arr[1] );
				} else {
					return intval( $arr[0] ) * 60;
				}
			} else {
				return intval( $time );
			}
		} else {
			return 0;
		}
	}

	/**
	 * Get POST data
	 *
	 * @param string $key The POST key to retrieve.
	 * @return string value
	 */
	public static function get_post_value( string $key ): string {
		if ( ! isset( $_POST[ $key ] ) ) {
			return '';
		}
		$value = wp_unslash( $_POST[ $key ] );

		if ( ' ' === $value ) {
			return ' ';
		}

		return sanitize_text_field( $value );
	}

	/**
	 * Get GET data
	 *
	 * @param string $key The GET key to retrieve.
	 * @return string value
	 */
	public static function get_get_value( string $key ): string {
		if ( ! isset( $_GET[ $key ] ) ) {
			return '';
		}
		return sanitize_text_field( wp_unslash( $_GET[ $key ] ) );
	}

	/**
	 * Get SERVER data
	 *
	 * @param string $key The SERVER key to retrieve.
	 * @return string value
	 */
	public static function get_server_value( string $key ): string {
		if ( ! isset( $_SERVER[ $key ] ) ) {
			return '';
		}
		return sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
	}

	/**
	 * Get COOKIE data
	 *
	 * @param string $key The COOKIE key to retrieve.
	 * @return string value
	 */
	public static function get_cookie_value( string $key ): string {
		if ( ! isset( $_COOKIE[ $key ] ) ) {
			return '';
		}
		return sanitize_text_field( wp_unslash( $_COOKIE[ $key ] ) );
	}

	/**
	 * Generate breadcrumb item HTML.
	 *
	 * @param string $url URL for the breadcrumb item.
	 * @param string $content Content for the breadcrumb item.
	 * @param bool   $maybe_link Whether to wrap in link.
	 * @param bool   $maybe_schema Whether to include schema markup.
	 * @param bool   $has_icon Whether content contains icon.
	 * @return string Generated breadcrumb item HTML.
	 */
	public static function breadcrumb_item( $url, $content, $maybe_link = true, $maybe_schema = true, $has_icon = false ) {

		$url     = esc_url( bricks_render_dynamic_data( $url ) );
		$content = $has_icon ? bricks_render_dynamic_data( $content ) : wp_kses_post( bricks_render_dynamic_data( $content ) );

		$wrapper_schema = $maybe_schema ? " itemprop='item'" : '';
		$content_schema = $maybe_schema ? " itemprop='name'" : '';

		return $maybe_link ? "<a href='" . $url . "'" . $wrapper_schema . '><span' . $content_schema . '>' . $content . '</span></a>' : '<span' . $wrapper_schema . '><span' . $content_schema . '>' . $content . '</span></span>';
	}



	/**
	 * Get miscellaneous options for BricksExtras.
	 *
	 * @return array Array of miscellaneous options.
	 */
	public static function get_extras_misc_options(): array {

		$options = array();

			$options = array(
				'interactions'      => array(
					'title'       => esc_html__( 'Element Interactions', 'bricksextras' ),
					'docs_slug'   => 'interactions',
					'description' => esc_html__( 'Triggers and Actions for elements', 'bricksextras' ),
				),
				'query_loop_extras' => array(
					'title'       => esc_html__( 'Query Loop Extras', 'bricksextras' ),
					'file_name'   => 'x-query-loop-extras',
					'description' => esc_html__( 'Adjacent Posts', 'bricksextras' ) . ', ' . esc_html__( 'Related Posts', 'bricksextras' ) . ', ' . esc_html__( 'WP Menu', 'bricksextras' ) . ', ' . esc_html__( 'Favorites', 'bricksextras' ) . ', ' . esc_html__( 'Gallery', 'bricksextras' ),
					'docs_slug'   => 'query-loop-extras',
					'stylesheet'  => false,
					'element'     => false,
				),
				'x_ray'             => array(
					'title'     => esc_html__( 'X-Ray Mode', 'bricksextras' ),
					'docs_slug' => 'x-ray-mode',
				),

			);

			return $options;
	}


	/**
	 * Get extras conditions by type.
	 *
	 * @param string $conditiontype Type of conditions to retrieve.
	 * @return array Array of conditions.
	 */
	public static function get_extras_conditions( $conditiontype ): array {

		$conditions = array();

		if ( 'general' === $conditiontype ) {

			$conditions = array(
				'archive_type'                           => array(
					'title'     => esc_html__( 'Archive type', 'bricksextras' ),
					'docs_slug' => 'conditions/#archive-type',
				),
				'at_least_1_search_result'               => array(
					'title'     => esc_html__( 'At least one search result', 'bricksextras' ),
					'docs_slug' => 'conditions/#at-least-one-search-result',
				),
				'author_has_cpt_entry'                   => array(
					'title'     => esc_html__( 'Author has CPT entry', 'bricksextras' ),
					'docs_slug' => 'conditions/#author-has-cpt-entry',
				),
				'authored_by_logged_in_user'             => array(
					'title'     => esc_html__( 'Authored by logged in user', 'bricksextras' ),
					'docs_slug' => 'conditions/#authored-by-loggedin-user',
				),
				'body_classes'                           => array(
					'title'     => esc_html__( 'Body classes', 'bricksextras' ),
					'docs_slug' => 'conditions/#body-classes',
				),
				'category_archive'                       => array(
					'title'     => esc_html__( 'Category archive', 'bricksextras' ),
					'docs_slug' => 'conditions/#category-archive',
				),
				'current_day'                            => array(
					'title'     => esc_html__( 'Current day (of month)', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-day-of-month',
				),
				'current_month'                          => array(
					'title'     => esc_html__( 'Current month', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-month',
				),
				'current_year'                           => array(
					'title'     => esc_html__( 'Current year', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-year',
				),
				'current_taxonomy_term_has_child'        => array(
					'title'     => esc_html__( 'Current taxonomy term has child', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-taxonomy-term-has-child',
				),
				'current_taxonomy_term_has_parent'       => array(
					'title'     => esc_html__( 'Current taxonomy term has parent', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-taxonomy-term-has-parent',
				),
				'current_taxonomy_term_is'               => array(
					'title'     => esc_html__( 'Current taxonomy term is', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-taxonomy-term-is',
				),
				'current_taxonomy_term_is_descendant_of' => array(
					'title'     => esc_html__( 'Current taxonomy term is descendant of', 'bricksextras' ),
					'docs_slug' => 'conditions/#current-taxonomy-term-is-descendant-of',
				),
				'cpt_has_at_least_1_entry'               => array(
					'title'     => esc_html__( 'CPT has at least 1 published entry', 'bricksextras' ),
					'docs_slug' => 'conditions/#cpt-has-at-least-1-published-entry',
				),
				'date_field_value'                       => array(
					'title'     => esc_html__( 'Date field value', 'bricksextras' ),
					'docs_slug' => 'conditions/#date-field-value',
				),
				'datetime_field_value'                   => array(
					'title'     => esc_html__( 'Datetime field value', 'bricksextras' ),
					'docs_slug' => 'conditions/#datetime-field-value',
				),
				'has_child_category'                     => array(
					'title'     => esc_html__( 'Has child category', 'bricksextras' ),
					'docs_slug' => 'conditions/#has-child-category',
				),
				'has_custom_excerpt'                     => array(
					'title'     => esc_html__( 'Has custom excerpt', 'bricksextras' ),
					'docs_slug' => 'conditions/#has-custom-excerpt',
				),
				'has_post_content'                       => array(
					'title'     => esc_html__( 'Has post content', 'bricksextras' ),
					'docs_slug' => 'conditions/#has-post-content',
				),
				'is_child'                               => array(
					'title'     => esc_html__( 'Is child', 'bricksextras' ),
					'docs_slug' => 'conditions/#is-child',
				),
				'is_parent'                              => array(
					'title'     => esc_html__( 'Is parent', 'bricksextras' ),
					'docs_slug' => 'conditions/#is-parent',
				),
				'in_favorites_loop'                      => array(
					'title'     => esc_html__( 'In favorites loop', 'bricksextras' ),
					'docs_slug' => 'conditions/#in-favorites-loop',
				),
				'polylang_language'                      => array(
					'title'     => esc_html__( 'Language', 'bricksextras' ) . ' (Polylang)',
					'docs_slug' => 'conditions/#language-polylang',
				),
				'translatepress_language'                => array(
					'title'     => esc_html__( 'Language', 'bricksextras' ) . ' (TranslatePress)',
					'docs_slug' => 'conditions/#language-translatepress',
				),
				'language_visitor'                       => array(
					'title'     => esc_html__( 'Language', 'bricksextras' ) . ' (' . esc_html__( 'visitor', 'bricksextras' ) . ')',
					'docs_slug' => 'conditions/#language-visitor',
				),
				'wpml_language'                          => array(
					'title'     => esc_html__( 'Language', 'bricksextras' ) . ' (WPML)',
					'docs_slug' => 'conditions/#language-wpml',
				),
				'loop_item_number'                       => array(
					'title'     => esc_html__( 'Loop item number', 'bricksextras' ),
					'docs_slug' => 'conditions/#loop-item-number',
				),
				'page_type'                              => array(
					'title'     => esc_html__( 'Page type', 'bricksextras' ),
					'docs_slug' => 'conditions/#page-type',
				),
				'post_category'                          => array(
					'title'     => esc_html__( 'Post category', 'bricksextras' ),
					'docs_slug' => 'conditions/#post-category',
				),
				'post_ancestor'                          => array(
					'title'     => esc_html__( 'Post ancestor', 'bricksextras' ),
					'docs_slug' => 'conditions/#post-ancestor',
				),
				'post_comment_count'                     => array(
					'title'     => esc_html__( 'Post comment count', 'bricksextras' ),
					'docs_slug' => 'conditions/#post-comment-count',
				),
				'page_parent'                            => array(
					'title'     => esc_html__( 'Page parent', 'bricksextras' ),
					'docs_slug' => 'conditions/#page-parent',
				),
				'post_publish_date'                      => array(
					'title'     => esc_html__( 'Post publish date', 'bricksextras' ),
					'docs_slug' => 'conditions/#post-publish-date',
				),
				'post_tag'                               => array(
					'title'     => esc_html__( 'Post tag', 'bricksextras' ),
					'docs_slug' => 'conditions/#post-tag',
				),
				'post_type'                              => array(
					'title'     => esc_html__( 'Post type', 'bricksextras' ),
					'docs_slug' => 'conditions/#post-type',
				),
				'published_during_last'                  => array(
					'title'     => esc_html__( 'Published during the last', 'bricksextras' ),
					'docs_slug' => 'conditions/#published-during-the-last',
				),
				'tag_archive'                            => array(
					'title'     => esc_html__( 'Tag archive', 'bricksextras' ),
					'docs_slug' => 'conditions/#tag-archive',
				),

			);

		} elseif ( 'member' === $conditiontype ) {

			$conditions = array(
				'easy_digital_downloads'    => array(
					'title'     => 'Easy Digital Downloads',
					'docs_slug' => 'easy-digital-downloads',
				),
				'fluentcart'                => array(
					'title'     => 'FluentCart',
					'docs_slug' => 'fluentcart',
				),
				'memberpress'               => array(
					'title'     => 'MemberPress',
					'docs_slug' => 'memberpress',
				),
				'pmp_membership_level'      => array(
					'title'     => 'Paid Memberships Pro',
					'docs_slug' => 'paid-memberships-pro',
				),
				'restrict_content'          => array(
					'title'     => 'Restrict Content',
					'docs_slug' => 'restrict-content',
				),
				'sure_members'              => array(
					'title'     => 'SureMembers',
					'docs_slug' => 'suremembers',
				),
				'wishlist_member'           => array(
					'title'     => 'WishList Member',
					'docs_slug' => 'wishlist-member',
				),
				'woocommerce_subscriptions' => array(
					'title'     => 'WooCommerce Subscriptions',
					'docs_slug' => 'woocommerce-subscriptions',
				),
				'woocommerce_memberships'   => array(
					'title'     => 'WooCommerce Memberships',
					'docs_slug' => 'woocommerce-memberships',
				),
				'wp_member'                 => array(
					'title'     => 'WP Members',
					'docs_slug' => 'wp-members',
				),

			);

		} elseif ( 'wc' === $conditiontype ) {

			$conditions = array(
				'cart_count'                     => array(
					'title'     => esc_html__( 'Cart count', 'bricksextras' ) . ' (' . esc_html__( 'items', 'bricksextras' ) . ')',
					'docs_slug' => 'woocommerce-conditions/#cart-count',
				),
				'cart_empty'                     => array(
					'title'     => esc_html__( 'Cart empty', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#cart-empty',
				),
				'cart_total'                     => array(
					'title'     => esc_html__( 'Cart total', 'bricksextras' ) . ' (' . esc_html__( 'cost', 'bricksextras' ) . ')',
					'docs_slug' => 'woocommerce-conditions/#cart-total',
				),
				'cart_total_minus_shipping'      => array(
					'title'     => esc_html__( 'Cart total', 'bricksextras' ) . ' (' . esc_html__( 'Excluding shipping', 'bricksextras' ) . ')',
					'docs_slug' => 'woocommerce-conditions/#cart-total',
				),
				'cart_weight'                    => array(
					'title'     => esc_html__( 'Cart weight', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#cart-weight',
				),
				'current_product_in_cart'        => array(
					'title'     => esc_html__( 'Current product in cart', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#current-product-in-cart',
				),
				'product_backorders_allowed'     => array(
					'title'     => esc_html__( 'Product allows backorders', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-allows-backorders',
				),
				'product_has_category'           => array(
					'title'     => esc_html__( 'Product has category', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-has-category',
				),
				'product_has_tag'                => array(
					'title'     => esc_html__( 'Product has tag', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-has-tag',
				),
				'product_crosssells_count'       => array(
					'title'     => esc_html__( 'Product cross-sells count', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-cross-sells-count',
				),
				'product_in_cart'                => array(
					'title'     => esc_html__( 'Product in cart', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-in-cart',
				),
				'product_in_cart_has_coupon'     => array(
					'title'     => esc_html__( 'Product in cart has a coupon applied', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-in-cart-has-a-coupon-applied',
				),
				'product_in_stock'               => array(
					'title'     => esc_html__( 'Product in stock', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-in-stock',
				),
				'product_on_backorder'           => array(
					'title'     => esc_html__( 'Product on backorder', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-on-backorder',
				),
				'product_is_downloadable'        => array(
					'title'     => esc_html__( 'Product is downloadable', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-is-downloadable',
				),
				'product_is_virtual'             => array(
					'title'     => esc_html__( 'Product is virtual', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-is-virtual',
				),
				'product_rating'                 => array(
					'title'     => esc_html__( 'Product rating', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-rating',
				),
				'product_type'                   => array(
					'title'     => esc_html__( 'Product type', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-type',
				),
				'product_upsell_count'           => array(
					'title'     => esc_html__( 'Product upsell count', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-upsell-count',
				),
				'product_weight'                 => array(
					'title'     => esc_html__( 'Product weight', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#product-weight',
				),
				'user_has_pending_order'         => array(
					'title'     => esc_html__( 'User has pending order', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#user-has-pending-order',
				),
				'user_has_order_with_status'     => array(
					'title'     => esc_html__( 'User has order with status', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#user-has-order-with-status',
				),
				'user_purchased_current_product' => array(
					'title'     => esc_html__( 'User purchased current product', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#user-purchased-current-product',
				),

				'user_purchased_product'         => array(
					'title'     => esc_html__( 'User purchased product', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#user-purchased-product',
				),
				'user_just_purchased_product'    => array(
					'title'     => esc_html__( 'User just purchased product', 'bricksextras' ) . ' (' . esc_html__( 'thankyou page', 'bricksextras' ),
					'docs_slug' => 'woocommerce-conditions/#user-just-purchased-product',
				),

			);

		}

		return $conditions;
	}


	/**
	 * Return minified script name.
	 *
	 * @param string $script_name Script name.
	 * @return string Script name with .min suffix.
	 */
	public static function maybe_minify_scripts( $script_name ) {
		return $script_name . '.min';
	}



	/**
	 * Get favorite IDs.
	 *
	 * @param mixed $list_name Optional list/post type to filter by.
	 * @return array Array of favorite post IDs.
	 */
	public static function get_favorite_ids_array( $list_name = false ) {

		$key           = 'x_favorite_ids';
		$cookie_prefix = 'x_favorite_ids__';
		$transient_key = sanitize_text_field( 'x_favorite_ids_transient_' . get_current_user_id() ); // Unique key for each user.
		$id_array      = array();

		// Check if user is logged in.
		if ( is_user_logged_in() ) {

			global $x_favorite_data_global;

			if ( $x_favorite_data_global && is_array( $x_favorite_data_global ) ) {
				$id_array = $x_favorite_data_global;
			} else {

				// Try to get data from transient.
				$id_array_transient = get_transient( $transient_key );

				if ( false !== $id_array_transient ) {

					if ( self::is_json( $id_array_transient ) && '' !== $id_array_transient ) {
						$decode_transient = json_decode( $id_array_transient, true );
						if ( json_last_error() === JSON_ERROR_NONE && is_array( $decode_transient ) ) {
							$id_array = $decode_transient;
						}
					} else {
						$id_array = array();
					}

					if ( json_last_error() !== JSON_ERROR_NONE ) {

						// Transient not found, fetch from user meta.
						$user_meta = get_user_meta( get_current_user_id(), $key, true );

						if ( $user_meta && self::is_json( $user_meta ) ) {
							$id_array = json_decode( $user_meta, true );
							if ( json_last_error() === JSON_ERROR_NONE && is_array( $id_array ) ) {
								$id_array = self::array_values_from_meta_as_integers( $id_array );
							} else {
								$id_array = array(); // Fallback if JSON error occurs.
							}
						} else {
							$favorite_data = self::get_favorite_data_from_cookie( $cookie_prefix );
							$id_array      = is_array( $favorite_data ) ? $favorite_data : array();
						}

						// Set a 12hr transient for caching.
						set_transient( $transient_key, wp_json_encode( $id_array ), 12 * HOUR_IN_SECONDS );

					}
				}
			}
		} else {

			// Use cookie for guests.
			$favorite_data = self::get_favorite_data_from_cookie( $cookie_prefix );
			$id_array      = is_array( $favorite_data ) ? $favorite_data : array();
		}

		// Retrieve only the post_type data if specified.
		$output = ( ! $list_name || ! empty( $id_array[ $list_name ] ) ) ? ( $id_array[ $list_name ] ?? array() ) : array();

		return is_array( $output ) ? array_values( array_filter( array_map( 'absint', $output ) ) ) : array();
	}




	/**
	 * Get favorite data from all relevant cookies.
	 *
	 * @param string $cookie_prefix Cookie prefix to search for.
	 * @return array Array of favorite data from cookies.
	 */
	private static function get_favorite_data_from_cookie( $cookie_prefix ): array {

		$favorite_data = array();

		// Iterate through all cookies to find those that start with the prefix.
		foreach ( $_COOKIE as $cookie_name => $cookie_value ) {

			$cookie_name = sanitize_text_field( $cookie_name );

			if ( strpos( $cookie_name, $cookie_prefix ) === 0 ) {

				// Remove prefix from cookie name for use as key.
				$key_name = substr( $cookie_name, strlen( $cookie_prefix ) );

				$cookie_value = wp_unslash( $cookie_value );

				if ( self::is_json( $cookie_value ) ) {

					$data = json_decode( $cookie_value, true );

					// Check if the JSON is valid & sanitize IDs as integers.
					if ( json_last_error() === JSON_ERROR_NONE && is_array( $data ) ) {
						$favorite_data[ $key_name ] = self::array_values_as_integers( $data );
					}
				}
			}
		}

		return $favorite_data;
	}


	/**
	 * Ensure IDs in nested arrays are integers.
	 *
	 * @param array $input_array Array to process.
	 * @return array Processed array with integer values.
	 */
	public static function array_values_from_meta_as_integers( $input_array ): array {

		if ( ! is_array( $input_array ) ) {
			return array();
		}

		$output_array = array();

		foreach ( $input_array as $input_key => $value ) {

			$key = sanitize_text_field( $input_key );

			if ( is_array( $value ) ) {
				// Use the arrayValuesAsIntegers function to process nested arrays.
				$output_array[ $key ] = self::array_values_as_integers( $value );
			} else {
				// If not an array, make it an empty array.
				$output_array[ $key ] = array();
			}
		}

		return $output_array;
	}


	/**
	 * Ensure all items in a flat array are integers.
	 *
	 * @param array $input_array Array to process.
	 * @return array Filtered array with only integer values.
	 */
	public static function array_values_as_integers( $input_array ): array {

		// If it's not an array, return an empty array.
		if ( ! is_array( $input_array ) ) {
			return array();
		}

		$max_items = apply_filters( 'bricksextras/favorites/max_items', 1000 );
		if ( count( $input_array ) > $max_items ) {
			$input_array = array_slice( $input_array, 0, $max_items );
		}

		// Filter the array to keep only integer values.
		$result = array();
		foreach ( $input_array as $value ) {
			$int_value = (int) $value;
			if ( $int_value > 0 && $int_value <= 99999999 ) {
				$result[] = $int_value;
			}
		}

		return $result;
	}

	/**
	 * Helper function to validate if a string is JSON.
	 *
	 * @param string $input_string String to validate.
	 * @return bool True if valid JSON, false otherwise.
	 */
	public static function is_json( $input_string ) {
		json_decode( $input_string );
		return ( json_last_error() === JSON_ERROR_NONE );
	}

	/**
	 * Get parent loop ID (taking into account components).
	 *
	 * @param array $element Element data.
	 * @param bool  $is_component Whether element is a component.
	 * @return mixed Loop ID or false.
	 */
	public static function get_parent_loop_id( $element, $is_component = false ) {

		$id = false;

		if ( method_exists( '\Bricks\Query', 'is_any_looping' ) ) {

			$query_id = \Bricks\Query::is_any_looping();

			if ( $query_id ) {

				if ( ! $is_component ) {
					$id = strtok( \Bricks\Query::get_query_element_id( $query_id ), '-' );

					if ( 'jet' === $id ) {
						$id = \Bricks\Query::get_query_element_id( $query_id );
					}
				} else {
					$id = isset( $element['parentComponent'] ) && $element['parentComponent'] ? $element['parentComponent'] : strtok( \Bricks\Query::get_query_element_id( $query_id ), '-' );

					if ( 'jet' === $id ) {
						$id = isset( $element['parentComponent'] ) && $element['parentComponent'] ? $element['parentComponent'] : \Bricks\Query::get_query_element_id( $query_id );
					}
				}
			}
		}

		return $id;
	}

	/**
	 * Check if an element is in component instance.
	 *
	 * @param array $element Element data.
	 * @return bool True if element is in component instance.
	 */
	public static function is_component_instance( $element ) {

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

		// Case 1: Element is a component root.
		$is_component_root = ! empty( $element['cid'] );

		// Case 2: Element is inside a component.
		$is_inside_component = ! empty( $element['instanceId'] ) && ! empty( $element['parentComponent'] );

		return $is_component_root || $is_inside_component;
	}

	/**
	 * Generate a unique identifier for an element, taking into account component context
	 * and the full component hierarchy in Bricks v2.
	 *
	 * @param array  $element    The element data array.
	 * @param string $element_id The original element identifier.
	 * @return string The unique identifier.
	 */
	public static function generate_unique_identifier( $element, $element_id ) {
		// If not in a component context, return original element ID.
		if ( ! self::is_component_instance( $element ) ) {
			return $element_id;
		}

		// Check if Bricks Frontend elements are available.
		if ( class_exists( '\Bricks\Frontend' ) && property_exists( '\Bricks\Frontend', 'elements' ) && ! empty( \Bricks\Frontend::$elements ) ) {
			// Build a unique path that includes instance IDs.
			$path = array();

			// Start with the element's instanceId (if available).
			if ( ! empty( $element['instanceId'] ) ) {
				// This is the direct container of the element.
				$container_id = $element['instanceId'];
				$path[]       = $container_id;

				// Now find the container element to get its instanceId (the wrapper).
				if ( isset( \Bricks\Frontend::$elements[ $container_id ] ) ) {
					$container_element = \Bricks\Frontend::$elements[ $container_id ];

					// If the container has an instanceId, that's our wrapper.
					if ( ! empty( $container_element['instanceId'] ) ) {
						$wrapper_id = $container_element['instanceId'];
						$path       = array( $wrapper_id, $container_id ); // Ensure wrapper is first.
					}
				}
			}

			// If we have a path, create a unique identifier.
			if ( ! empty( $path ) ) {
				// Add the element ID to the end of the path.
				$final_id = implode( '__', $path ) . '__' . $element_id;
				return esc_attr( $final_id );
			}
		}

		// Fallback: If we couldn't use Bricks Frontend elements or find instance IDs,
		// use the component ID if available.
		if ( ! empty( $element['cid'] ) ) {
			return $element['cid'] . '__' . $element_id;
		}

		// Last resort: return the original element ID.
		return $element_id;
	}

	/**
	 * Get parent component ID from element data.
	 *
	 * @param array $element Element data.
	 * @return mixed Component ID or false.
	 */
	public static function get_parent_component_id( $element ) {

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

		if ( ! self::is_component_instance( $element ) ) {
			return false;
		}

		if ( empty( $element['parentComponent'] ) ) {
			return false;
		}

		return sanitize_text_field( $element['parentComponent'] );
	}



	/**
	 * Normalize image settings for poster images.
	 *
	 * @param array  $settings Element settings.
	 * @param string $setting_name The name of the image setting (default: 'image').
	 * @param bool   $force_original_image Force original image URL for 'full' size (default: false).
	 * @return array Normalized image settings.
	 */
	public static function get_normalized_image_settings( $settings, $setting_name = 'image', $force_original_image = false ) {

		if ( empty( $settings[ $setting_name ] ) ) {
			return array(
				'id'   => 0,
				'url'  => false,
				'size' => \BRICKS_DEFAULT_IMAGE_SIZE,
			);
		}

		$image = $settings[ $setting_name ];

		// Size.
		$image['size'] = empty( $image['size'] ) ? \BRICKS_DEFAULT_IMAGE_SIZE : $settings[ $setting_name ]['size'];

		if ( ! empty( $image['useDynamicData'] ) ) {

			// Try with 'image' type first as it's most appropriate for our use case.
			$dynamic_content = \Bricks\Integrations\Dynamic_Data\Providers::render_tag( $image['useDynamicData'], 'image', array( 'size' => $image['size'] ) );

			// If we got a result, process it.
			if ( ! empty( $dynamic_content ) ) {
				// Handle different return types from dynamic data.
				if ( is_numeric( $dynamic_content ) ) {
					// It's an attachment ID.
					$image['id'] = $dynamic_content;
					// Clear any existing URL to ensure we generate from the ID.
					unset( $image['url'] );
				} elseif ( is_array( $dynamic_content ) && ! empty( $dynamic_content[0] ) ) {
					$content = $dynamic_content[0];

					if ( is_numeric( $content ) ) {
						// It's an attachment ID in an array.
						$image['id'] = $content;
						// Clear any existing URL to ensure we generate from the ID.
						unset( $image['url'] );
					} elseif ( is_string( $content ) ) {
						if ( strpos( $content, '<img' ) !== false ) {
							// Extract the src attribute from HTML.
							preg_match( '/src=["\']([^"\']*)["\']/i', $content, $matches );
							if ( ! empty( $matches[1] ) ) {
								$image['url'] = $matches[1];
							}
						} else {
							// It's probably a direct URL.
							$image['url'] = $content;
						}
					}
				} elseif ( is_string( $dynamic_content ) ) {
					// Direct string result.
					if ( strpos( $dynamic_content, '<img' ) !== false ) {
						// Extract the src attribute from HTML.
						preg_match( '/src=["\']([^"\']*)["\']/i', $dynamic_content, $matches );
						if ( ! empty( $matches[1] ) ) {
							$image['url'] = $matches[1];
						}
					} else {
						// It's probably a direct URL.
						$image['url'] = $dynamic_content;
					}
				}
			}
		}

		$image['id'] = empty( $image['id'] ) ? 0 : $image['id'];

		if ( ! isset( $image['url'] ) ) {
			$image['url'] = ! empty( $image['id'] ) ? wp_get_attachment_image_url( $image['id'], $image['size'] ) : false;
		}

		// Force original image URL if requested and conditions are met.
		if ( $force_original_image && 'full' === $image['size'] && ! empty( $image['id'] ) ) {
			// Check if current URL contains 'scaled' indicating WordPress has scaled the image.
			if ( isset( $image['url'] ) && strpos( $image['url'], '-scaled.' ) !== false ) {
				$original_url = wp_get_original_image_url( $image['id'] );

				if ( $original_url && $original_url !== $image['url'] ) {
					$image['url'] = $original_url;
				}
			}
		}

		return $image;
	}


	/**
	 * Set a unique identifier attribute for an element, taking into account loop context.
	 *
	 * @param object $element_instance The element instance (typically $this in an element class).
	 * @param string $attribute_name The attribute name to set (default: 'data-x-id').
	 * @param string $prefix Optional prefix to add to the identifier.
	 * @return string The generated identifier.
	 */
	public static function set_identifier_attribute( $element_instance, $attribute_name = 'data-x-id', $prefix = '' ) {

		$element_id = $element_instance->id;
		$element    = $element_instance->element;

		// Start with the element ID.
		$identifier       = self::generate_unique_identifier( $element, $element_id );
		$final_identifier = $identifier;

		// Handle loop context if Bricks Query class exists and has the required method.
		if ( method_exists( '\Bricks\Query', 'is_any_looping' ) ) {
			$query_id = \Bricks\Query::is_any_looping();

			if ( $query_id ) {
				$loop_index = false;

				// Check for nested loops up to 2 levels deep.
				if ( self::get_bricks_looping_parent_query_id_by_level( 2 ) ) {
					// We're in a doubly-nested loop.
					$loop_index = \Bricks\Query::get_query_for_element_id(
						\Bricks\Query::get_query_element_id( self::get_bricks_looping_parent_query_id_by_level( 2 ) )
					)->loop_index . '_' .
					\Bricks\Query::get_query_for_element_id(
						\Bricks\Query::get_query_element_id( self::get_bricks_looping_parent_query_id_by_level( 1 ) )
					)->loop_index . '_' .
					\Bricks\Query::get_loop_index();
				} elseif ( self::get_bricks_looping_parent_query_id_by_level( 1 ) ) {
					// We're in a singly-nested loop.
					$loop_index = \Bricks\Query::get_query_for_element_id(
						\Bricks\Query::get_query_element_id( self::get_bricks_looping_parent_query_id_by_level( 1 ) )
					)->loop_index . '_' . \Bricks\Query::get_loop_index();
				} else {
					// We're in a simple loop.
					$loop_index = \Bricks\Query::get_loop_index();
				}

				// Append the loop index to make the identifier unique within loops.
				$final_identifier = $identifier . '_' . $loop_index;
			}
		}

		// Add prefix if provided.
		if ( ! empty( $prefix ) ) {
			$final_identifier = $prefix . '_' . $final_identifier;
		}

		// Set the attribute on the element's root.
		$element_instance->set_attribute( '_root', $attribute_name, esc_attr( $final_identifier ) );

		// Return the identifier in case it's needed for other operations.
		return esc_attr( $final_identifier );
	}

	/**
	 * Get the array of user IDs who favorited a post
	 *
	 * @param int $post_id The ID of the post to check.
	 * @return array Array of user IDs who favorited the post, or empty array if none or if post meta is disabled.
	 */
	public static function get_favorite_users( $post_id ) {
		// Check if post meta functionality is enabled.
		$use_post_meta = apply_filters( 'bricksextras/favorite_post_meta', true );
		if ( ! $use_post_meta ) {
			return array();
		}

		$post_meta_key  = 'x_favorite_users';
		$favorite_users = get_post_meta( $post_id, $post_meta_key, true );

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

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

		// Check for JSON errors or non-array result.
		if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $favorite_users ) ) {
			return array();
		}

		$favorite_users = array_values( array_filter( array_map( 'absint', $favorite_users ) ) );

		// Return array of user IDs.
		return $favorite_users;
	}


	/**
	 * Get the count of users who favorited a post
	 *
	 * @param int $post_id The ID of the post to check.
	 * @return int Number of users who favorited the post, or 0 if none or if post meta is disabled.
	 */
	public static function get_favorite_users_count( $post_id ) {

		// Check if post meta functionality is enabled.
		$use_post_meta = apply_filters( 'bricksextras/favorite_post_meta', true );
		if ( ! $use_post_meta ) {
			return 0;
		}

		$count = get_post_meta( $post_id, 'x_favorite_users_count', true );

		// If count is not set, fall back to counting the users array.
		if ( empty( $count ) && '0' !== $count ) {
			$users = self::get_favorite_users( $post_id );
			$count = count( $users );
		}

		return (int) $count;
	}


	/**
	 * Get an image tag with optional placeholder fallback
	 *
	 * @param array $attributes Optional attributes to add to the image tag.
	 * @param bool  $url_only If true, return only the URL. If false, return full HTML img tag.
	 * @return string URL or HTML for the image
	 */
	public static function placeholder_image( $attributes = array(), $url_only = false ) {

		// Check if custom src is provided in attributes.
		$custom_src = isset( $attributes['src'] ) ? $attributes['src'] : '';

		// Get placeholder URL as fallback.
		$image_url = '';
		if ( method_exists( '\Bricks\Builder', 'get_template_placeholder_image' ) ) {
			$image_url = \Bricks\Builder::get_template_placeholder_image();
		}

		// Use custom src if provided, otherwise use placeholder.
		$final_url = $custom_src ? $custom_src : $image_url;

		if ( ! $final_url ) {
			return '';
		}

		if ( $url_only ) {
			return esc_url( $final_url );
		} else {

			$attr_string = '';
			if ( ! empty( $attributes ) && is_array( $attributes ) ) {
				foreach ( $attributes as $key => $value ) {
					if ( 'src' !== $key ) {
						$attr_string .= ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
					}
				}
			}

			// Set default alt if not provided.
			$alt_text = isset( $attributes['alt'] ) ? '' : ' alt="Placeholder Image"';

			return '<img' . $alt_text . ' src="' . esc_url( $final_url ) . '"' . $attr_string . '>';
		}
	}

	/**
	 * Helper function for get_nestable_children pattern.
	 *
	 * @param object $element_instance The element instance.
	 * @param int    $count           Number of children to generate (default 1).
	 * @return array Array of nestable children.
	 */
	public static function get_nestable_children_helper( $element_instance, $count = 1 ) {
		$children = array();

		for ( $i = 0; $i < $count; $i++ ) {
			$item = $element_instance->get_nestable_item();

			// Replace {item_index} with actual index.
			$item       = wp_json_encode( $item );
			$item       = str_replace( '{item_index}', $i + 1, $item );
			$item       = json_decode( $item, true );
			$children[] = $item;
		}

		return $children;
	}
}
