<?php
/**
 * WooCommerce Hooks Integration
 *
 * @package Ugcify\Integrations\WooCommerce
 */

namespace Ugcify\Integrations\WooCommerce;

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

/**
 * Class Hooks
 */
class Hooks {
	/**
	 * Option name for A/B tests definitions.
	 *
	 * @var string
	 */
	private const AB_TESTS_OPTION_NAME = 'ugcify_ab_tests';

	/**
	 * Option name for A/B test analytics counters.
	 *
	 * @var string
	 */
	private const AB_ANALYTICS_OPTION_NAME = 'ugcify_ab_test_analytics';

	/**
	 * Cart key used to persist lightbox attribution payload.
	 *
	 * @var string
	 */
	private const CART_TRACKING_KEY = 'ugcify_lightbox_tracking';

	/**
	 * Cart key used to persist A/B attribution payload for any add-to-cart source.
	 *
	 * @var string
	 */
	private const CART_AB_TRACKING_KEY = 'ugcify_ab_tracking';

	/**
	 * Order item meta key that stores normalized lightbox attribution payload.
	 *
	 * @var string
	 */
	private const ORDER_ITEM_TRACKING_META_KEY = '_ugcify_lightbox_tracking';

	/**
	 * Order item meta key that stores normalized A/B attribution payload.
	 *
	 * @var string
	 */
	private const ORDER_ITEM_AB_TRACKING_META_KEY = '_ugcify_ab_tracking';

	/**
	 * Order meta key that flags if a checkout includes at least one UGC-attributed line.
	 *
	 * @var string
	 */
	private const ORDER_META_HAS_SALE_KEY = '_ugcify_has_lightbox_sale';

	/**
	 * Order meta key that stores a summary payload for fast dashboard reads.
	 *
	 * @var string
	 */
	private const ORDER_META_SUMMARY_KEY = '_ugcify_lightbox_summary';

	/**
	 * Order meta key for tracked revenue.
	 *
	 * @var string
	 */
	private const ORDER_META_REVENUE_KEY = '_ugcify_lightbox_revenue';

	/**
	 * Order meta key for tracked review IDs.
	 *
	 * @var string
	 */
	private const ORDER_META_REVIEW_IDS_KEY = '_ugcify_lightbox_review_ids';

	/**
	 * Order meta key for tracked UGC IDs.
	 *
	 * @var string
	 */
	private const ORDER_META_UGC_IDS_KEY = '_ugcify_lightbox_ugcify_ids';

	/**
	 * Order meta key for total tracked quantity.
	 *
	 * @var string
	 */
	private const ORDER_META_TRACKED_QUANTITY_KEY = '_ugcify_lightbox_tracked_quantity';

	/**
	 * Order meta key for total tracked line item count.
	 *
	 * @var string
	 */
	private const ORDER_META_ITEMS_COUNT_KEY = '_ugcify_lightbox_items_count';

	/**
	 * Order meta key for review IDs that already received lightbox purchase rewards.
	 *
	 * @var string
	 */
	private const ORDER_META_REWARDED_REVIEW_IDS_KEY = '_ugcify_lightbox_rewarded_review_ids';

	/**
	 * Form renderer instance
	 *
	 * @var \Ugcify\Form\FormRenderer
	 */
	private $form_renderer;

	/**
	 * AB storefront runtime utility.
	 *
	 * @var ABVariantRuntime
	 */
	private $ab_variant_runtime;

	/**
	 * Constructor
	 *
	 * @param \Ugcify\Form\FormRenderer $form_renderer Form renderer instance.
	 */
	public function __construct( $form_renderer ) {
		$this->form_renderer = $form_renderer;
		$this->ab_variant_runtime = new ABVariantRuntime();
		$this->init_hooks();
	}

	/**
	 * Initialize hooks
	 *
	 * @return void
	 */
	private function init_hooks() {
		if ( $this->is_widget_enabled() ) {
			// Replace default WooCommerce product rating in single product summary.
			add_action( 'template_redirect', array( $this, 'replace_product_rating' ), 10 );

			// Add inline CSS to hide any remaining WooCommerce ratings as fallback.
			add_action( 'wp_enqueue_scripts', array( $this, 'hide_default_rating_css' ) );

			// Replace default reviews tab with our custom reviews section.
			add_filter( 'woocommerce_product_tabs', function( $tabs ) {

				if ( isset( $tabs['reviews'] ) ) {
					$product_id = get_the_ID();
					if ( ! $this->should_render_reviews_in_default_tab( $product_id ) ) {
						unset( $tabs['reviews'] );
						return $tabs;
					}

					$tabs['reviews']['callback'] = array( $this, 'render_reviews_tab_content' );
				}
				return $tabs;
			}, 98 );

			// Render UGC before/after related products when Position A/B custom variant is active.
			add_action( 'woocommerce_after_single_product_summary', array( $this, 'render_custom_reviews_before_related_products' ), 19 );
			add_action( 'woocommerce_after_single_product_summary', array( $this, 'render_custom_reviews_after_related_products' ), 21 );

			add_filter( 'woocommerce_product_tabs', function ( $tabs ) {

				if ( isset( $tabs['reviews'] ) ) {

					$product_id = get_the_ID();

					$repository          = new \Ugcify\Database\ReviewRepository();
					$custom_review_count = $repository->count(
						array(
							'product_id' => $product_id,
							'status'     => 'approved',
						)
					);

					$tabs['reviews']['title'] = sprintf(
						/* translators: %d: number of product reviews */ __( 'Reviews (%d)', 'ugcify' ),
						$custom_review_count
					);
				}

				return $tabs;
			}, 99 );

			// Replace comments template to prevent duplicate reviews on some themes.
			add_filter( 'comments_template', array( $this, 'replace_comments_template' ), 999 );

			// Remove WooCommerce review blocks in FSE/block themes to prevent duplicate reviews.
			add_filter( 'render_block', array( $this, 'remove_wc_review_blocks' ), 10, 2 );
		}

		// Track lightbox add-to-cart attribution and persist it through checkout.
		add_filter( 'woocommerce_add_cart_item_data', array( $this, 'attach_ab_tracking_to_cart_item_data' ), 10, 4 );
		add_action( 'woocommerce_add_to_cart', array( $this, 'track_lightbox_add_to_cart' ), 10, 6 );
		add_filter( 'woocommerce_get_cart_item_from_session', array( $this, 'restore_lightbox_tracking_from_session' ), 10, 3 );
		add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'add_tracking_meta_to_order_line_item' ), 10, 4 );
		add_action( 'woocommerce_checkout_order_processed', array( $this, 'persist_order_lightbox_tracking_summary' ), 10, 3 );
		add_action( 'woocommerce_order_status_changed', array( $this, 'refresh_tracking_on_order_status_change' ), 10, 4 );

		// Track AB test conversions when order completes.
		add_action( 'woocommerce_order_status_completed', array( $this, 'track_ab_test_conversion_from_order' ), 10, 2 );
		add_action( 'woocommerce_order_status_completed', array( $this, 'award_lightbox_purchase_points_from_order' ), 20, 2 );

		// Apply storefront AB image variant before WooCommerce image markup is generated.
		add_action( 'template_redirect', array( $this, 'prime_product_ab_variant_context' ), 1 );
		add_filter( 'woocommerce_product_get_image_id', array( $this, 'filter_product_image_id_for_ab_variant' ), 10, 2 );
		add_filter( 'woocommerce_product_variation_get_image_id', array( $this, 'filter_variation_image_id_for_ab_variant' ), 10, 2 );
		add_filter( 'woocommerce_product_get_gallery_image_ids', array( $this, 'filter_product_gallery_ids_for_ab_variant' ), 10, 2 );
		add_filter( 'woocommerce_available_variation', array( $this, 'filter_available_variation_for_ab_variant' ), 10, 3 );
		add_filter( 'woocommerce_cart_item_product', array( $this, 'filter_cart_item_product_for_ab_variant' ), 10, 3 );
		add_filter( 'woocommerce_cart_item_thumbnail', array( $this, 'filter_cart_item_thumbnail_for_ab_variant' ), 10, 3 );
		add_filter( 'woocommerce_store_api_cart_item_images', array( $this, 'filter_store_api_cart_item_images_for_ab_variant' ), 10, 3 );
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_single_product_ab_variation_script' ), 30 );
	}

	/**
	 * Determine whether the storefront review widget is enabled.
	 *
	 * The Review Template page controls widget activation through
	 * ugcify_review_template_settings.activeTemplateId. Keep enableWidget
	 * as a backward-compatible fallback for legacy setups.
	 *
	 * @return bool
	 */
	private function is_widget_enabled() {
		$gallery_settings = get_option( 'ugcify_review_template_settings', array() );
		$active_id        = isset( $gallery_settings['activeTemplateId'] )
			? sanitize_key( (string) $gallery_settings['activeTemplateId'] )
			: '';

		if ( '' !== $active_id ) {
			return true;
		}

		$templates = isset( $gallery_settings['templates'] ) && is_array( $gallery_settings['templates'] )
			? $gallery_settings['templates']
			: array();

		foreach ( $templates as $template ) {
			if ( is_array( $template ) && ! empty( $template['enabled'] ) ) {
				return true;
			}
		}

		$template_settings = $this->get_template_customization_settings();

		return ! empty( $template_settings['enableWidget'] );
	}

	/**
	 * Render custom reviews in the default WooCommerce Reviews tab when applicable.
	 *
	 * @return void
	 */
	public function render_reviews_tab_content() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		if ( ! $this->should_render_reviews_in_default_tab( $product->get_id() ) ) {
			return;
		}

		$this->render_custom_reviews_section();
	}

	/**
	 * Render custom reviews before related products when position test resolves to that placement.
	 *
	 * @return void
	 */
	public function render_custom_reviews_before_related_products() {
		$this->render_custom_reviews_for_position( 'before_related_products' );
	}

	/**
	 * Render custom reviews after related products when position test resolves to that placement.
	 *
	 * @return void
	 */
	public function render_custom_reviews_after_related_products() {
		$this->render_custom_reviews_for_position( 'after_related_products' );
	}

	/**
	 * Conditionally render custom reviews section for a resolved custom position.
	 *
	 * @param string $target_position Position key to render for.
	 * @return void
	 */
	private function render_custom_reviews_for_position( $target_position ) {
		if ( ! function_exists( 'is_product' ) || ! is_product() ) {
			return;
		}

		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		$product_id = $product->get_id();
		$resolved_position = $this->ab_variant_runtime->resolve_ugcify_position( $product_id, 'default' );

		if ( $resolved_position !== $target_position ) {
			return;
		}

		$this->render_custom_reviews_section();
	}

	/**
	 * Determine whether UGC should render inside default Reviews tab.
	 *
	 * @param int $product_id Product ID.
	 * @return bool
	 */
	private function should_render_reviews_in_default_tab( $product_id ) {
		$product_id = absint( $product_id );
		if ( $product_id < 1 ) {
			return true;
		}

		$resolved_position = $this->ab_variant_runtime->resolve_ugcify_position( $product_id, 'default' );
		return 'default' === $resolved_position;
	}

	/**
	 * Prime AB assignment context for current single product request.
	 *
	 * @return void
	 */
	public function prime_product_ab_variant_context() {
		$context = $this->ab_variant_runtime->prime_product_context();
		if ( empty( $context['has_test'] ) ) {
			return;
		}

		do_action( 'ugcify_ab_variant_assigned', $context['test_id'], $context['product_id'], $context['variant'] );
	}

	/**
	 * Override product featured image for assigned AB variant.
	 *
	 * @param int         $image_id Current featured image attachment ID.
	 * @param \WC_Product $product Product object.
	 * @return int
	 */
	public function filter_product_image_id_for_ab_variant( $image_id, $product ) {
		if ( ! $product instanceof \WC_Product ) {
			return $image_id;
		}

		return $this->ab_variant_runtime->resolve_featured_image_id( $product->get_id(), $image_id );
	}

	/**
	 * Override product gallery image IDs for assigned AB variant.
	 *
	 * @param array       $gallery_ids Current gallery image IDs.
	 * @param \WC_Product $product Product object.
	 * @return array
	 */
	public function filter_product_gallery_ids_for_ab_variant( $gallery_ids, $product ) {
		if ( ! is_product() || ! $product instanceof \WC_Product ) {
			return $gallery_ids;
		}

		return $this->ab_variant_runtime->resolve_gallery_image_ids( $product->get_id(), $gallery_ids );
	}

	/**
	 * Override variation image ID so variable product image swaps keep AB variant image.
	 *
	 * @param int                   $image_id Variation image ID.
	 * @param \WC_Product_Variation $variation Variation product.
	 * @return int
	 */
	public function filter_variation_image_id_for_ab_variant( $image_id, $variation ) {
		if ( ! function_exists( 'is_product' ) || ! is_product() || ! $variation instanceof \WC_Product_Variation ) {
			return $image_id;
		}

		$parent_id = absint( $variation->get_parent_id() );
		if ( $parent_id < 1 ) {
			return $image_id;
		}

		$parent_product = wc_get_product( $parent_id );
		if ( ! $parent_product instanceof \WC_Product ) {
			return $image_id;
		}

		$parent_fallback_id = absint( $parent_product->get_image_id() );
		$resolved_parent_id = $this->ab_variant_runtime->resolve_featured_image_id( $parent_id, $parent_fallback_id );

		return $resolved_parent_id > 0 ? $resolved_parent_id : $image_id;
	}

	/**
	 * Override variation image payload consumed by wc-add-to-cart-variation.js.
	 *
	 * @param array                $variation_data Variation payload.
	 * @param \WC_Product          $product Parent product.
	 * @param \WC_Product_Variation $variation Variation object.
	 * @return array
	 */
	public function filter_available_variation_for_ab_variant( $variation_data, $product, $variation ) {
		if ( ! function_exists( 'is_product' ) || ! is_product() ) {
			return $variation_data;
		}

		if ( ! is_array( $variation_data ) || ! $product instanceof \WC_Product || ! $variation instanceof \WC_Product_Variation ) {
			return $variation_data;
		}

		$parent_id = absint( $product->get_id() );
		$fallback_id = absint( $product->get_image_id() );
		$resolved_id = $this->ab_variant_runtime->resolve_featured_image_id( $parent_id, $fallback_id );

		if ( $resolved_id < 1 ) {
			return $variation_data;
		}

		$image_data = $this->build_wc_variation_image_payload( $resolved_id, $product );
		if ( empty( $image_data ) ) {
			return $variation_data;
		}

		$variation_data['image_id'] = $resolved_id;
		$variation_data['image']    = $image_data;

		return $variation_data;
	}

	/**
	 * Override mini-cart/cart drawer thumbnail with assigned AB variant image.
	 *
	 * @param string $thumbnail Existing thumbnail HTML.
	 * @param array  $cart_item Cart item payload.
	 * @param string $cart_item_key Cart item key.
	 * @return string
	 */
	public function filter_cart_item_thumbnail_for_ab_variant( $thumbnail, $cart_item, $cart_item_key ) {
		unset( $cart_item_key );

		if ( ! is_array( $cart_item ) || empty( $cart_item['product_id'] ) ) {
			return $thumbnail;
		}

		$product = isset( $cart_item['data'] ) && $cart_item['data'] instanceof \WC_Product
			? $cart_item['data']
			: wc_get_product( absint( $cart_item['product_id'] ) );
		if ( ! $product instanceof \WC_Product ) {
			return $thumbnail;
		}

		$ab_image_id = $this->resolve_cart_item_ab_image_id( $cart_item, $product );
		if ( $ab_image_id < 1 ) {
			return $thumbnail;
		}

		$resolved_html = wp_get_attachment_image( $ab_image_id, 'woocommerce_thumbnail' );
		return $resolved_html ? $resolved_html : $thumbnail;
	}

	/**
	 * Override cart/checkout product object image so all Woo templates read the assigned AB variant.
	 *
	 * @param \WC_Product $product Cart line product object.
	 * @param array       $cart_item Cart item payload.
	 * @param string      $cart_item_key Cart item key.
	 * @return \WC_Product
	 */
	public function filter_cart_item_product_for_ab_variant( $product, $cart_item, $cart_item_key ) {
		unset( $cart_item_key );

		if ( ! $product instanceof \WC_Product || ! is_array( $cart_item ) ) {
			return $product;
		}

		$ab_image_id = $this->resolve_cart_item_ab_image_id( $cart_item, $product );
		if ( $ab_image_id < 1 || ! method_exists( $product, 'set_image_id' ) ) {
			return $product;
		}

		$product = clone $product;
		$product->set_image_id( $ab_image_id );

		return $product;
	}

	/**
	 * Override Store API cart item images used by Woo blocks (mini-cart/cart/checkout).
	 *
	 * @param array  $product_images Existing Store API image objects.
	 * @param array  $cart_item Cart item payload.
	 * @param string $cart_item_key Cart item key.
	 * @return array
	 */
	public function filter_store_api_cart_item_images_for_ab_variant( $product_images, $cart_item, $cart_item_key ) {
		unset( $cart_item_key );

		if ( ! is_array( $cart_item ) || empty( $cart_item['product_id'] ) ) {
			return $product_images;
		}

		$product = isset( $cart_item['data'] ) && $cart_item['data'] instanceof \WC_Product
			? $cart_item['data']
			: wc_get_product( absint( $cart_item['product_id'] ) );

		if ( ! $product instanceof \WC_Product ) {
			return $product_images;
		}

		$ab_image_id = $this->resolve_cart_item_ab_image_id( $cart_item, $product );
		if ( $ab_image_id < 1 ) {
			return $product_images;
		}

		$full = wp_get_attachment_image_src( $ab_image_id, 'full' );
		$thumb = wp_get_attachment_image_src( $ab_image_id, 'woocommerce_thumbnail' );
		if ( ! is_array( $full ) || empty( $full[0] ) || ! is_array( $thumb ) || empty( $thumb[0] ) ) {
			return $product_images;
		}

		return array(
			(object) array(
				'id'        => (int) $ab_image_id,
				'src'       => (string) $full[0],
				'thumbnail' => (string) $thumb[0],
				'srcset'    => (string) wp_get_attachment_image_srcset( $ab_image_id, 'full' ),
				'sizes'     => (string) wp_get_attachment_image_sizes( $ab_image_id, 'full' ),
				'name'      => (string) get_the_title( $ab_image_id ),
				'alt'       => (string) get_post_meta( $ab_image_id, '_wp_attachment_image_alt', true ),
			),
		);
	}

	/**
	 * Resolve image ID for the exact AB variant stored on cart item payload.
	 *
	 * @param array       $cart_item Cart item payload.
	 * @param \WC_Product $product Product object.
	 * @return int
	 */
	private function resolve_cart_item_ab_image_id( $cart_item, $product ) {
		$ab_tracking = isset( $cart_item[ self::CART_AB_TRACKING_KEY ] ) && is_array( $cart_item[ self::CART_AB_TRACKING_KEY ] )
			? $this->sanitize_ab_tracking( $cart_item[ self::CART_AB_TRACKING_KEY ] )
			: array();

		if ( empty( $ab_tracking ) ) {
			return 0;
		}

		$stored_image_id = ! empty( $ab_tracking['image_id'] ) ? absint( $ab_tracking['image_id'] ) : 0;
		if ( $stored_image_id > 0 ) {
			return $stored_image_id;
		}

		$variant = ! empty( $ab_tracking['variant'] ) ? sanitize_key( (string) $ab_tracking['variant'] ) : 'original';
		if ( 'ugcify' !== $variant ) {
			return absint( $product->get_image_id() );
		}

		$product_id = ! empty( $ab_tracking['product_id'] ) ? absint( $ab_tracking['product_id'] ) : 0;
		$test_id    = ! empty( $ab_tracking['test_id'] ) ? sanitize_key( (string) $ab_tracking['test_id'] ) : '';

		if ( $product_id < 1 && ! empty( $cart_item['product_id'] ) ) {
			$product_id = absint( $cart_item['product_id'] );
		}

		return $this->get_ab_variant_featured_image_id_for_cart( $product_id, $test_id );
	}

	/**
	 * Attach AB tracking payload before WooCommerce creates cart item.
	 * This guarantees cart, offcanvas, and checkout all receive stable variant image data.
	 *
	 * @param array $cart_item_data Cart item data.
	 * @param int   $product_id Product ID.
	 * @param int   $variation_id Variation ID.
	 * @param int   $quantity Quantity.
	 * @return array
	 */
	public function attach_ab_tracking_to_cart_item_data( $cart_item_data, $product_id, $variation_id, $quantity ) {
		unset( $quantity );

		if ( ! is_array( $cart_item_data ) ) {
			$cart_item_data = array();
		}

		// Attach share token to cart item data for ALL add-to-cart events (lightbox
		// and regular). This ensures the token reaches the order item meta even when
		// the customer adds to cart from the regular product page after landing via
		// a share link.
		$ugc_share = isset( $_REQUEST['ugc_share'] ) ? sanitize_key( wp_unslash( $_REQUEST['ugc_share'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( '' !== $ugc_share ) {
			$cart_item_data['ugcify_share_token']      = $ugc_share;
			$cart_item_data['ugcify_share_self_click'] = ! empty( $_REQUEST['ugcify_share_self_click'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		}

		$ab_tracking = $this->build_ab_tracking_payload( $product_id, $variation_id );
		if ( empty( $ab_tracking ) ) {
			return $cart_item_data;
		}

		$cart_item_data[ self::CART_AB_TRACKING_KEY ] = $ab_tracking;

		return $cart_item_data;
	}

	/**
	 * Get AB variation2 featured image ID from running image test payload.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $test_id Optional test ID.
	 * @return int
	 */
	private function get_ab_variant_featured_image_id_for_cart( $product_id, $test_id = '' ) {
		$product_id = absint( $product_id );
		$test_id    = sanitize_key( (string) $test_id );

		if ( $product_id < 1 ) {
			return 0;
		}

		// Read the running A/B test directly from WordPress options.
		$storage = get_option( 'ugcify_ab_tests', array( 'items' => array() ) );
		$items   = isset( $storage['items'] ) && is_array( $storage['items'] ) ? $storage['items'] : array();

		foreach ( $items as $test ) {
			if ( ! is_array( $test ) ) {
				continue;
			}

			$test_item_id    = isset( $test['id'] ) ? sanitize_key( (string) $test['id'] ) : '';
			$test_product_id = isset( $test['product_id'] ) ? absint( $test['product_id'] ) : 0;
			$test_status     = isset( $test['status'] ) ? sanitize_key( (string) $test['status'] ) : '';
			$tab_key         = isset( $test['tab_key'] ) ? sanitize_key( (string) $test['tab_key'] ) : '';
			$test_type       = isset( $test['test_type'] ) ? strtolower( sanitize_text_field( (string) $test['test_type'] ) ) : '';
			$is_image_test   = 'image' === $tab_key || 'image' === $test_type;

			if ( $test_id && $test_item_id !== $test_id ) {
				continue;
			}

			// Skip if not matching product or not running.
			if ( $test_product_id !== $product_id || 'running' !== $test_status || ! $is_image_test ) {
				continue;
			}

			// Get variation 2 images from test data.
			$variation_defaults = isset( $test['variation_defaults'] ) && is_array( $test['variation_defaults'] )
				? $test['variation_defaults']
				: array();

			$variation2_images = isset( $variation_defaults['variation2_images'] ) && is_array( $variation_defaults['variation2_images'] )
				? $variation_defaults['variation2_images']
				: array();

			foreach ( $variation2_images as $variation_image ) {
				$image_id = 0;

				if ( is_array( $variation_image ) ) {
					if ( ! empty( $variation_image['id'] ) ) {
						$image_id = absint( $variation_image['id'] );
					} elseif ( ! empty( $variation_image['attachment_id'] ) ) {
						$image_id = absint( $variation_image['attachment_id'] );
					} elseif ( ! empty( $variation_image['url'] ) ) {
						$image_url = esc_url_raw( (string) $variation_image['url'] );
						$image_id  = absint( attachment_url_to_postid( $image_url ) );
					}
				} elseif ( is_string( $variation_image ) ) {
					$image_url = esc_url_raw( $variation_image );
					$image_id  = absint( attachment_url_to_postid( $image_url ) );
				}

				if ( $image_id > 0 ) {
					return $image_id;
				}
			}
		}

		return 0;
	}

	/**
	 * Add lightweight JS fallback so variation image swaps stay on AB image.
	 *
	 * @return void
	 */
	public function enqueue_single_product_ab_variation_script() {
		if ( ! function_exists( 'is_product' ) || ! is_product() || ! wp_script_is( 'wc-add-to-cart-variation', 'enqueued' ) ) {
			return;
		}

		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		$fallback_id = absint( $product->get_image_id() );
		$resolved_id = $this->ab_variant_runtime->resolve_featured_image_id( $product->get_id(), $fallback_id );
		if ( $resolved_id < 1 || $resolved_id === $fallback_id ) {
			return;
		}

		$image_payload = $this->build_wc_variation_image_payload( $resolved_id, $product );
		if ( empty( $image_payload ) ) {
			return;
		}

		$script = 'window.ugcAbVariationImage = ' . wp_json_encode( $image_payload ) . ';'
			. '(function($){if(!window.ugcAbVariationImage){return;}'
			. '$(document.body).on("found_variation", "form.variations_form", function(e, v){'
			. 'if(!v){return;}v.image=window.ugcAbVariationImage;v.image_id=window.ugcAbVariationImage.id;'
			. '});})(jQuery);';

		wp_add_inline_script( 'wc-add-to-cart-variation', $script, 'after' );
	}

	/**
	 * Build WooCommerce-compatible variation image payload.
	 *
	 * @param int         $attachment_id Attachment ID.
	 * @param \WC_Product $product Product.
	 * @return array
	 */
	private function build_wc_variation_image_payload( $attachment_id, $product ) {
		$attachment_id = absint( $attachment_id );
		if ( $attachment_id < 1 ) {
			return array();
		}

		$full = wp_get_attachment_image_src( $attachment_id, 'full' );
		$thumb = wp_get_attachment_image_src( $attachment_id, 'woocommerce_thumbnail' );
		$gallery_thumb = wp_get_attachment_image_src( $attachment_id, 'woocommerce_gallery_thumbnail' );

		if ( ! $full ) {
			return array();
		}

		$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
		if ( '' === (string) $alt && $product instanceof \WC_Product ) {
			$alt = $product->get_name();
		}

		return array(
			'id'                   => $attachment_id,
			'src'                  => esc_url_raw( $full[0] ),
			'full_src'             => esc_url_raw( $full[0] ),
			'thumb_src'            => $thumb ? esc_url_raw( $thumb[0] ) : esc_url_raw( $full[0] ),
			'gallery_thumbnail_src'=> $gallery_thumb ? esc_url_raw( $gallery_thumb[0] ) : esc_url_raw( $full[0] ),
			'src_w'                => isset( $full[1] ) ? absint( $full[1] ) : 0,
			'src_h'                => isset( $full[2] ) ? absint( $full[2] ) : 0,
			'full_src_w'           => isset( $full[1] ) ? absint( $full[1] ) : 0,
			'full_src_h'           => isset( $full[2] ) ? absint( $full[2] ) : 0,
			'srcset'               => (string) wp_get_attachment_image_srcset( $attachment_id, 'full' ),
			'sizes'                => (string) wp_get_attachment_image_sizes( $attachment_id, 'full' ),
			'alt'                  => (string) $alt,
			'title'                => (string) get_the_title( $attachment_id ),
			'caption'              => (string) wp_get_attachment_caption( $attachment_id ),
		);
	}

	/**
	 * Replace default WooCommerce product rating with custom UGC rating.
	 *
	 * @return void
	 */
	public function replace_product_rating() {
		if ( ! is_product() ) {
			return;
		}

		// Remove default WooCommerce rating at all potential hook points and priorities.
		remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_rating', 10 );
		remove_action( 'woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_rating', 5 );
		
		// Remove at various priorities in case theme overrides.
		for ( $priority = 1; $priority <= 50; $priority++ ) {
			remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_rating', $priority );
		}

		// Add custom UGC Commerce rating.
		add_action( 'woocommerce_single_product_summary', array( $this, 'render_custom_product_rating' ), 10 );
		
		// Completely disable WooCommerce native reviews/rating functionality.
		add_filter( 'woocommerce_product_get_rating_html', '__return_empty_string', 999 );
		add_filter( 'woocommerce_product_get_review_count', array( $this, 'get_custom_review_count' ), 10, 2 );
		add_filter( 'woocommerce_product_get_rating_count', array( $this, 'get_custom_rating_count' ), 10, 2 );
	}

	/**
	 * Add inline CSS to hide default WooCommerce rating elements.
	 *
	 * @return void
	 */
	public function hide_default_rating_css(): void {
		if ( ! is_product() ) {
			return;
		}
		$css = '
			.woocommerce-product-rating:not(.ugcify-product-rating),
			.product_meta + .woocommerce-product-rating:not(.ugcify-product-rating),
			.woocommerce div.product .woocommerce-product-rating:not(.ugcify-product-rating) {
				display: none !important;
			}
			.ugcify-product-rating {
				display: block !important;
			}
		';
		wp_register_style( 'ugcify-rating-hide', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters
		wp_enqueue_style( 'ugcify-rating-hide' );
		wp_add_inline_style( 'ugcify-rating-hide', $css );
	}

	/**
	 * Get custom review count for a product.
	 *
	 * @param int        $count Current count (from WooCommerce).
	 * @param \WC_Product $product Product object.
	 * @return int Custom review count.
	 */
	public function get_custom_review_count( $count, $product ) {
		if ( ! $product instanceof \WC_Product ) {
			return $count;
		}

		$product_id = $product->get_id();
		if ( ! $product_id ) {
			return $count;
		}

		$repository = new \Ugcify\Database\ReviewRepository();
		return $repository->count_rated_reviews( $product_id, 'approved' );
	}

	/**
	 * Get custom rating count for a product.
	 *
	 * @param int        $count Current count (from WooCommerce).
	 * @param \WC_Product $product Product object.
	 * @return int Custom rating count.
	 */
	public function get_custom_rating_count( $count, $product ) {
		return $this->get_custom_review_count( $count, $product );
	}

	/**
	 * Render custom product rating in single product summary.
	 *
	 * @return void
	 */
	public function render_custom_product_rating() {
		global $product;

		if ( ! $product instanceof \WC_Product ) {
			return;
		}

		$product_id = $product->get_id();
		if ( ! $product_id ) {
			return;
		}

		$repository    = new \Ugcify\Database\ReviewRepository();
		$avg_rating    = $repository->get_average_rating( $product_id, 'approved' );
		$total_reviews = $repository->count( array(
			'product_id' => $product_id,
			'status'     => 'approved',
		) );

		// Only display if there are reviews.
		if ( $total_reviews < 1 ) {
			return;
		}

		// Check if template exists.
		$template = UGCIFY_PATH . 'templates/product-rating.php';
		
		if ( file_exists( $template ) ) {
			include $template;
		} else {
			// Fallback inline rendering.
			$this->render_inline_product_rating( $avg_rating, $total_reviews );
		}
	}

	/**
	 * Render inline product rating (fallback if template doesn't exist).
	 *
	 * @param float $avg_rating Average rating.
	 * @param int   $total_reviews Total number of reviews.
	 * @return void
	 */
	private function render_inline_product_rating( $avg_rating, $total_reviews ) {
		?>
		<div class="woocommerce-product-rating ugcify-product-rating">
			<div class="ugcify-rating-wrapper">
				<div class="ugcify-stars-display">
					<?php
					for ( $i = 1; $i <= 5; $i++ ) {
						$star_type = 'empty';
						if ( $i <= floor( $avg_rating ) ) {
							$star_type = 'full';
						} elseif ( $i <= ceil( $avg_rating ) && ( $avg_rating - floor( $avg_rating ) >= 0.5 ) ) {
							$star_type = 'half';
						}
						echo '<span class="ugcify-star-display ' . esc_attr( $star_type ) . '"></span>';
					}
					?>
				</div>
				<div class="ugcify-rating-text">
					<span class="ugcify-rating-average"><?php echo number_format( $avg_rating, 1 ); ?></span>
					<a href="#reviews" class="woocommerce-review-link ugcify-review-link" rel="nofollow">
						<?php
						printf(
							/* translators: %s: number of reviews */
							esc_html( _n( '(%s review)', '(%s reviews)', $total_reviews, 'ugcify' ) ),
							'<span class="count">' . esc_html( number_format_i18n( $total_reviews ) ) . '</span>'
						);
						?>
					</a>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Refresh tracking summary/meta when order status changes (e.g. manual completion).
	 *
	 * @param int            $order_id Order ID.
	 * @param string         $from_status Previous status.
	 * @param string         $to_status New status.
	 * @param \WC_Order|null $order Order object.
	 * @return void
	 */
	public function refresh_tracking_on_order_status_change( $order_id, $from_status, $to_status, $order ) {
		unset( $from_status, $to_status );
		$this->persist_order_lightbox_tracking_summary( $order_id, array(), $order );
	}

	/**
	 * Attach or merge lightbox attribution on the cart item after WooCommerce adds it.
	 *
	 * @param string $cart_item_key Cart item key.
	 * @param int    $product_id Product ID.
	 * @param int    $quantity Added quantity.
	 * @param int    $variation_id Variation ID.
	 * @param array  $variation Variation attributes.
	 * @param array  $cart_item_data Cart item payload.
	 * @return void
	 */
	public function track_lightbox_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
		unset( $variation, $cart_item_data );

		if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
			return;
		}

		if ( empty( WC()->cart->cart_contents[ $cart_item_key ] ) || ! is_array( WC()->cart->cart_contents[ $cart_item_key ] ) ) {
			return;
		}

		$ab_tracking = isset( WC()->cart->cart_contents[ $cart_item_key ][ self::CART_AB_TRACKING_KEY ] ) && is_array( WC()->cart->cart_contents[ $cart_item_key ][ self::CART_AB_TRACKING_KEY ] )
			? $this->sanitize_ab_tracking( WC()->cart->cart_contents[ $cart_item_key ][ self::CART_AB_TRACKING_KEY ] )
			: array();

		if ( empty( $ab_tracking ) ) {
			$ab_tracking = $this->build_ab_tracking_payload( $product_id, $variation_id );
		}

		if ( ! empty( $ab_tracking ) ) {
			WC()->cart->cart_contents[ $cart_item_key ][ self::CART_AB_TRACKING_KEY ] = $ab_tracking;
		}

		$ab_context = array(
			'test_id' => ! empty( $ab_tracking['test_id'] ) ? sanitize_key( (string) $ab_tracking['test_id'] ) : '',
			'variant' => ! empty( $ab_tracking['variant'] ) ? sanitize_key( (string) $ab_tracking['variant'] ) : 'original',
		);

		$tracking_payload = $this->get_lightbox_tracking_request_data();
		if ( empty( $tracking_payload ) ) {
			WC()->cart->set_session();
			return;
		}

		$existing_tracking = array();
		$ab_variant = ! empty( $ab_context['variant'] ) ? sanitize_key( (string) $ab_context['variant'] ) : 'original';
		if ( ! in_array( $ab_variant, array( 'original', 'ugcify' ), true ) ) {
			$ab_variant = 'original';
		}
		if ( isset( WC()->cart->cart_contents[ $cart_item_key ][ self::CART_TRACKING_KEY ] ) ) {
			$existing_tracking = WC()->cart->cart_contents[ $cart_item_key ][ self::CART_TRACKING_KEY ];
		}

		$merged_tracking = $this->merge_cart_tracking(
			$existing_tracking,
			$tracking_payload,
			$quantity,
			$product_id,
			$variation_id
		);

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

		$merged_tracking['ab_variant'] = $ab_variant;
		$merged_tracking['ab_test_id'] = isset( $ab_context['test_id'] ) ? sanitize_key( (string) $ab_context['test_id'] ) : '';
		WC()->cart->cart_contents[ $cart_item_key ][ self::CART_TRACKING_KEY ] = $merged_tracking;
		WC()->cart->set_session();

		// Record modal add-to-cart intent for A/B analytics.
		$this->increment_ab_metric_for_product( $product_id, $ab_variant, 'add_to_cart_modal_count' );
	}

	/**
	 * Build normalized AB cart tracking payload including a stable image ID.
	 *
	 * @param int $product_id Product ID.
	 * @param int $variation_id Variation ID.
	 * @return array
	 */
	private function build_ab_tracking_payload( $product_id, $variation_id ) {
		$product_id   = absint( $product_id );
		$variation_id = absint( $variation_id );

		$ab_context = $this->ab_variant_runtime->get_tracking_context( $product_id );
		if ( empty( $ab_context['has_test'] ) || empty( $ab_context['test_id'] ) ) {
			return array();
		}

		$variant = ! empty( $ab_context['variant'] ) ? sanitize_key( (string) $ab_context['variant'] ) : 'original';
		if ( ! in_array( $variant, array( 'original', 'ugcify' ), true ) ) {
			$variant = 'original';
		}

		$source = isset( $_REQUEST['ugcify_lightbox_source'] ) ? sanitize_key( wp_unslash( $_REQUEST['ugcify_lightbox_source'] ) ) : 'product'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified upstream by WooCommerce add-to-cart nonce.
		if ( '' === $source ) {
			$source = 'product';
		}

		$base_product = wc_get_product( $variation_id > 0 ? $variation_id : $product_id );
		$image_id     = $base_product instanceof \WC_Product ? absint( $base_product->get_image_id() ) : 0;

		if ( 'ugcify' === $variant ) {
			$image_context = $this->ab_variant_runtime->get_variant_image_context( $product_id );
			if ( ! empty( $image_context['can_override_images'] ) && ! empty( $image_context['featured_attachment_id'] ) ) {
				$image_id = absint( $image_context['featured_attachment_id'] );
			}
		}

		return array(
			'test_id'    => sanitize_key( (string) $ab_context['test_id'] ),
			'product_id' => $product_id,
			'variant'    => $variant,
			'source'     => $source,
			'image_id'   => $image_id,
		);
	}

	/**
	 * Restore tracking payload from cart session data.
	 *
	 * @param array  $cart_item Cart item array.
	 * @param array  $values Stored cart session value.
	 * @param string $cart_item_key Cart item key.
	 * @return array
	 */
	public function restore_lightbox_tracking_from_session( $cart_item, $values, $cart_item_key ) {
		unset( $cart_item_key );

		if ( ! empty( $values[ self::CART_AB_TRACKING_KEY ] ) && is_array( $values[ self::CART_AB_TRACKING_KEY ] ) ) {
			$ab_tracking = $this->sanitize_ab_tracking( $values[ self::CART_AB_TRACKING_KEY ] );
			if ( ! empty( $ab_tracking ) ) {
				$cart_item[ self::CART_AB_TRACKING_KEY ] = $ab_tracking;
			}
		}

		// Restore share token for regular add-to-cart.
		if ( ! empty( $values['ugcify_share_token'] ) ) {
			$cart_item['ugcify_share_token']      = sanitize_key( (string) $values['ugcify_share_token'] );
			$cart_item['ugcify_share_self_click'] = ! empty( $values['ugcify_share_self_click'] );
		}

		if ( empty( $values[ self::CART_TRACKING_KEY ] ) ) {
			return $cart_item;
		}

		$tracking = $this->sanitize_cart_tracking( $values[ self::CART_TRACKING_KEY ] );
		if ( empty( $tracking ) ) {
			return $cart_item;
		}

		$cart_item[ self::CART_TRACKING_KEY ] = $tracking;

		return $cart_item;
	}

	/**
	 * Persist tracked cart attribution into order line item meta.
	 *
	 * @param \WC_Order_Item_Product $item Order line item.
	 * @param string                 $cart_item_key Cart item key.
	 * @param array                  $values Cart item values.
	 * @param \WC_Order              $order Order object.
	 * @return void
	 */
	public function add_tracking_meta_to_order_line_item( $item, $cart_item_key, $values, $order ) {
		unset( $cart_item_key, $order );

		if ( ! empty( $values[ self::CART_AB_TRACKING_KEY ] ) && is_array( $values[ self::CART_AB_TRACKING_KEY ] ) ) {
			$ab_tracking = $this->sanitize_ab_tracking( $values[ self::CART_AB_TRACKING_KEY ] );
			if ( ! empty( $ab_tracking ) ) {
				$item->add_meta_data( self::ORDER_ITEM_AB_TRACKING_META_KEY, $ab_tracking, true );
			}
		}

		// Persist share token for regular (non-lightbox) add-to-cart so conversion
		// points can be awarded to the sharer at order completion.
		if ( ! empty( $values['ugcify_share_token'] ) ) {
			$item->add_meta_data( '_ugcify_share_token', sanitize_key( (string) $values['ugcify_share_token'] ), true );
			$item->add_meta_data( '_ugcify_share_self_click', ! empty( $values['ugcify_share_self_click'] ) ? '1' : '0', true );
		}

		if ( empty( $values[ self::CART_TRACKING_KEY ] ) ) {
			return;
		}

		$line_quantity = isset( $values['quantity'] ) ? absint( $values['quantity'] ) : absint( $item->get_quantity() );
		$tracking      = $this->normalize_tracking_for_line_item( $values[ self::CART_TRACKING_KEY ], $line_quantity );

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

		$item->add_meta_data( self::ORDER_ITEM_TRACKING_META_KEY, $tracking, true );
		$item->add_meta_data( '_ugcify_source_flag', 'ugcify_lightbox', true );
		$item->add_meta_data( '_ugcify_source', $tracking['source'], true );
		$item->add_meta_data( '_ugcify_review_ids', $tracking['review_ids'], true );
		$item->add_meta_data( '_ugcify_ids', $tracking['ugcify_ids'], true );
		$item->add_meta_data( '_ugcify_tracked_quantity', absint( $tracking['tracked_quantity'] ), true );
		$item->add_meta_data( '_ugcify_share_token', isset( $tracking['ugc_share'] ) ? sanitize_key( (string) $tracking['ugc_share'] ) : '', true );
		$item->add_meta_data( '_ugcify_ab_variant', isset( $tracking['ab_variant'] ) ? $tracking['ab_variant'] : 'original', true );
		$item->add_meta_data( '_ugcify_ab_test_id', isset( $tracking['ab_test_id'] ) ? $tracking['ab_test_id'] : '', true );
	}

	/**
	 * Persist order-level summary meta so dashboard queries stay cheap.
	 *
	 * @param int            $order_id Order ID.
	 * @param array|string   $posted_data Posted checkout payload.
	 * @param \WC_Order|null $order Order object.
	 * @return void
	 */
	public function persist_order_lightbox_tracking_summary( $order_id, $posted_data = array(), $order = null ) {
		unset( $posted_data );

		if ( ! $order instanceof \WC_Order ) {
			$order = wc_get_order( $order_id );
		}

		if ( ! $order ) {
			return;
		}

		$summary = array(
			'source_flag'      => 'ugcify_lightbox',
			'items_count'      => 0,
			'tracked_quantity' => 0,
			'tracked_revenue'  => 0.0,
			'review_ids'       => array(),
			'ugcify_ids'          => array(),
		);

		foreach ( $order->get_items( 'line_item' ) as $item ) {
			if ( ! $item instanceof \WC_Order_Item_Product ) {
				continue;
			}

			$tracking = $item->get_meta( self::ORDER_ITEM_TRACKING_META_KEY, true );
			$tracking = $this->normalize_tracking_for_line_item( $tracking, $item->get_quantity() );

			if ( empty( $tracking ) ) {
				continue;
			}

			$line_quantity    = max( 1, absint( $item->get_quantity() ) );
			$tracked_quantity = min( $line_quantity, absint( $tracking['tracked_quantity'] ) );
			$line_total       = (float) $item->get_total();
			$tracked_ratio    = min( 1, max( 0, $tracked_quantity / $line_quantity ) );

			$tracking['tracked_quantity'] = $tracked_quantity;

			// Keep line item meta synced with any quantity edits made after add-to-cart.
			$item->update_meta_data( self::ORDER_ITEM_TRACKING_META_KEY, $tracking );
			$item->update_meta_data( '_ugcify_source_flag', 'ugcify_lightbox' );
			$item->update_meta_data( '_ugcify_source', $tracking['source'] );
			$item->update_meta_data( '_ugcify_review_ids', $tracking['review_ids'] );
			$item->update_meta_data( '_ugcify_ids', $tracking['ugcify_ids'] );
			$item->update_meta_data( '_ugcify_tracked_quantity', $tracked_quantity );
			$item->update_meta_data( '_ugcify_share_token', isset( $tracking['ugc_share'] ) ? sanitize_key( (string) $tracking['ugc_share'] ) : '' );
			$item->update_meta_data( '_ugcify_ab_variant', isset( $tracking['ab_variant'] ) ? $tracking['ab_variant'] : 'original' );
			$item->update_meta_data( '_ugcify_ab_test_id', isset( $tracking['ab_test_id'] ) ? $tracking['ab_test_id'] : '' );
			$item->save();

			++$summary['items_count'];
			$summary['tracked_quantity'] += $tracked_quantity;
			$summary['tracked_revenue']  += $line_total * $tracked_ratio;
			$summary['review_ids']        = array_merge( $summary['review_ids'], $tracking['review_ids'] );
			$summary['ugcify_ids']           = array_merge( $summary['ugcify_ids'], $tracking['ugcify_ids'] );
		}

		if ( 0 === $summary['items_count'] ) {
			$this->clear_order_lightbox_tracking_meta( $order );
			return;
		}

		$summary['review_ids']      = $this->unique_positive_ints( $summary['review_ids'] );
		$summary['ugcify_ids']         = $this->unique_positive_ints( $summary['ugcify_ids'] );
		$summary['tracked_revenue'] = (float) wc_format_decimal( $summary['tracked_revenue'], wc_get_price_decimals() );

		$order->update_meta_data( self::ORDER_META_HAS_SALE_KEY, 'yes' );
		$order->update_meta_data( self::ORDER_META_SUMMARY_KEY, $summary );
		$order->update_meta_data( self::ORDER_META_REVENUE_KEY, $summary['tracked_revenue'] );
		$order->update_meta_data( self::ORDER_META_REVIEW_IDS_KEY, $summary['review_ids'] );
		$order->update_meta_data( self::ORDER_META_UGC_IDS_KEY, $summary['ugcify_ids'] );
		$order->update_meta_data( self::ORDER_META_TRACKED_QUANTITY_KEY, absint( $summary['tracked_quantity'] ) );
		$order->update_meta_data( self::ORDER_META_ITEMS_COUNT_KEY, absint( $summary['items_count'] ) );
		$order->save();
	}

	/**
	 * Track AB test conversions when an order is marked as completed.
	 *
	 * @param int                $order_id Order ID.
	 * @param \WC_Order|null $order Order object.
	 * @return void
	 */
	public function track_ab_test_conversion_from_order( $order_id, $order = null ) {
		if ( ! $order instanceof \WC_Order ) {
			$order = wc_get_order( $order_id );
		}

		if ( ! $order ) {
			return;
		}

		// Already tracked conversions mark to previous runs.
		if ( $order->get_meta( '_ugcify_ab_conversions_tracked' ) === '1' ) {
			return;
		}

		// Get AB tests configuration.
		$ab_tests_storage = get_option(
			self::AB_TESTS_OPTION_NAME,
			array( 'items' => array() )
		);
		$ab_tests_items = isset( $ab_tests_storage['items'] ) && is_array( $ab_tests_storage['items'] )
			? $ab_tests_storage['items']
			: array();

		// Build quick maps for resolution by test ID and product ID.
		$tests_by_id       = array();
		$product_tests_map = array();
		foreach ( $ab_tests_items as $test ) {
			if ( ! is_array( $test ) ) {
				continue;
			}

			$product_id = isset( $test['product_id'] ) ? absint( $test['product_id'] ) : 0;
			$test_id    = isset( $test['id'] ) ? sanitize_key( (string) $test['id'] ) : '';
			$tab_key    = isset( $test['tab_key'] ) ? sanitize_key( (string) $test['tab_key'] ) : '';
			$test_type  = isset( $test['test_type'] ) ? strtolower( sanitize_text_field( (string) $test['test_type'] ) ) : '';
			$is_supported_test = in_array( $tab_key, array( 'image', 'gallery', 'ugcify-position' ), true ) || in_array( $test_type, array( 'image', 'layout', 'position' ), true );

			if ( $test_id && $is_supported_test ) {
				$tests_by_id[ $test_id ] = $test;
			}

			if ( $product_id > 0 && $test_id && $is_supported_test ) {
				$product_tests_map[ $product_id ] = $test;
			}
		}

		if ( empty( $product_tests_map ) ) {
			$order->update_meta_data( '_ugcify_ab_conversions_tracked', '1' );
			$order->save();
			return;
		}

		// Process each order item.
		foreach ( $order->get_items( 'line_item' ) as $item ) {
			if ( ! $item instanceof \WC_Order_Item_Product ) {
				continue;
			}

			if ( '1' === (string) $item->get_meta( '_ugcify_ab_conversion_tracked', true ) ) {
				continue;
			}

			$product_id = (int) $item->get_product_id();

			// Get the AB test variant from cart tracking meta.
			$cart_tracking = $item->get_meta( self::ORDER_ITEM_TRACKING_META_KEY, true );
			$cart_tracking = is_array( $cart_tracking ) ? $cart_tracking : array();
			$ab_item_tracking = $item->get_meta( self::ORDER_ITEM_AB_TRACKING_META_KEY, true );
			$ab_item_tracking = is_array( $ab_item_tracking ) ? $ab_item_tracking : array();
			$tracked_test_id = ! empty( $cart_tracking['ab_test_id'] )
				? sanitize_key( (string) $cart_tracking['ab_test_id'] )
				: ( ! empty( $ab_item_tracking['test_id'] ) ? sanitize_key( (string) $ab_item_tracking['test_id'] ) : '' );

			$test = array();
			if ( $tracked_test_id && isset( $tests_by_id[ $tracked_test_id ] ) ) {
				$test = $tests_by_id[ $tracked_test_id ];
			} elseif ( isset( $product_tests_map[ $product_id ] ) ) {
				$test = $product_tests_map[ $product_id ];
			}

			if ( empty( $test ) ) {
				continue;
			}

			// Extract AB variant from line item tracking; fallback to default.
			$variant = 'original';
			if ( ! empty( $cart_tracking['ab_variant'] ) ) {
				$variant = sanitize_key( (string) $cart_tracking['ab_variant'] );
				if ( ! in_array( $variant, array( 'original', 'ugcify' ), true ) ) {
					$variant = 'original';
				}
			} elseif ( ! empty( $ab_item_tracking['variant'] ) ) {
				$variant = sanitize_key( (string) $ab_item_tracking['variant'] );
				if ( ! in_array( $variant, array( 'original', 'ugcify' ), true ) ) {
					$variant = 'original';
				}
			}

			// Use the Analytics service to increment conversion metric.
			$test_id = isset( $test['id'] ) ? sanitize_key( (string) $test['id'] ) : '';
			if ( ! $test_id ) {
				continue;
			}

			$this->increment_ab_metric( $test, $variant, 'conversion_count' );

			$line_revenue = (float) $item->get_total();
			if ( $line_revenue > 0 ) {
				$this->increment_ab_revenue_metric( $test, $variant, 'conversion_revenue_total', $line_revenue );
			}

			$item->update_meta_data( '_ugcify_ab_conversion_tracked', '1' );
			$item->save();
		}

		// Mark order as processed to avoid re-tracking.
		$order->update_meta_data( '_ugcify_ab_conversions_tracked', '1' );
		$order->save();

		do_action( 'ugcify_ab_conversion_recorded', $order_id, $order );
	}

	/**
	 * Award configured lightbox purchase bonus points when a tracked order completes.
	 *
	 * Awards once per unique attributed review on the order, only for approved
	 * reviews owned by registered users, and skips self-purchases.
	 *
	 * @param int            $order_id Order ID.
	 * @param \WC_Order|null $order Order object.
	 * @return void
	 */
	public function award_lightbox_purchase_points_from_order( $order_id, $order = null ) {
		if ( ! $order instanceof \WC_Order ) {
			$order = wc_get_order( $order_id );
		}

		if ( ! $order ) {
			return;
		}

		$this->persist_order_lightbox_tracking_summary( $order_id, array(), $order );

		$review_ids = $this->unique_positive_ints( (array) $order->get_meta( self::ORDER_META_REVIEW_IDS_KEY, true ) );
		if ( empty( $review_ids ) ) {
			return;
		}

		$already_awarded = $this->unique_positive_ints( (array) $order->get_meta( self::ORDER_META_REWARDED_REVIEW_IDS_KEY, true ) );
		$remaining_ids   = array_values( array_diff( $review_ids, $already_awarded ) );

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

		$repository = new \Ugcify\Database\ReviewRepository();

		if ( ! class_exists( '\\Ugcify\\Points\\PointsManager' ) ) {
			return;
		}

		$manager    = new \Ugcify\Points\PointsManager();
		$updated    = false;

		foreach ( $remaining_ids as $review_id ) {
			$review = $repository->find( $review_id );
			if ( ! $review ) {
				continue;
			}

			$review_data = $review->to_array();
			$user_id     = absint( $review_data['user_id'] ?? 0 );

			if ( 0 === $user_id || 'approved' !== ( $review_data['status'] ?? '' ) ) {
				continue;
			}

			if ( $this->is_lightbox_self_purchase( $order, $user_id ) ) {
				continue;
			}

			$row_id = $manager->award_lightbox_purchase_points( $review_id, $user_id, (int) $order->get_id() );
			if ( ! $row_id ) {
				continue;
			}

			$reward_settings = \Ugcify\Admin\API\RewardSettingsAPI::get_reward_settings();
			$points_awarded  = absint( $reward_settings['pts_lightbox_purchase'] ?? 0 );

			do_action(
				'ugcify_reward_lightbox_purchase',
				array(
					'review_id'      => $review_id,
					'user_id'        => $user_id,
					'order_id'       => (int) $order->get_id(),
					'review'         => $review_data,
					'points_awarded' => $points_awarded,
				)
			);

			$already_awarded[] = $review_id;
			$updated           = true;
		}

		if ( ! $updated ) {
			$this->maybe_award_share_conversion_points_from_order( $order );
			return;
		}

		$order->update_meta_data( self::ORDER_META_REWARDED_REVIEW_IDS_KEY, $this->unique_positive_ints( $already_awarded ) );
		$order->save();
		$this->maybe_award_share_conversion_points_from_order( $order );
	}

	/**
	 * Award share token add-to-cart points once.
	 *
	 * @param array  $tracking Tracking payload.
	 * @param string $stage add_to_cart|conversion.
	 * @return void
	 */
	private function maybe_award_share_token_points( array $tracking, $stage, $order = null ) {
		$token = isset( $tracking['ugc_share'] ) ? sanitize_key( (string) $tracking['ugc_share'] ) : '';
		if ( '' === $token || ! class_exists( '\\Ugcify\\Database\\ReviewRepository' ) || ! class_exists( '\\Ugcify\\Points\\PointsManager' ) || ! \Ugcify\Helpers\FeatureGate::is_enabled( 'advanced_gamification' ) ) {
			return;
		}

		// Respect the social sharing toggle in reward settings.
		$reward_settings = \Ugcify\Admin\API\RewardSettingsAPI::get_reward_settings();
		if ( empty( $reward_settings['social_share_enabled'] ) ) {
			return;
		}

		$repository = new \Ugcify\Database\ReviewRepository();
		$share_meta = $repository->find_by_share_token( $token );
		if ( ! is_array( $share_meta ) ) {
			return;
		}

		$sharer_id = absint( $share_meta['author_id'] ?? 0 );
		$review_id = absint( $share_meta['review_id'] ?? 0 );
		if ( $sharer_id < 1 ) {
			return;
		}

		// Self-purchase guard: if the buyer is the same person who created the
		// share link, no points are awarded to anyone for this token.
		if ( $order instanceof \WC_Order && $this->is_lightbox_self_purchase( $order, $sharer_id ) ) {
			return;
		}

		// Self-click guard: if the person who clicked the link is the sharer
		// themselves (flagged by the frontend), skip all point awards.
		if ( ! empty( $tracking['self_click'] ) ) {
			return;
		}

		// Only 'conversion' stage is supported — add_to_cart no longer earns points.
		if ( 'conversion' !== $stage ) {
			return;
		}

		// Award sharer points once per token.
		if ( empty( $share_meta['awarded']['conversion'] ) ) {
			$manager = new \Ugcify\Points\PointsManager();
			$result  = $manager->credit_with_reference_key(
				$sharer_id,
				'pts_share_conversion',
				__( 'Social share purchase completed', 'ugcify' ),
				$review_id,
				'social_share_conversion',
				sprintf( 'social_share_conversion:pts_share_conversion:%s', $token ),
				array( 'token' => $token )
			);
			if ( ! empty( $result ) ) {
				$share_meta['awarded']['conversion'] = true;
				$repository->update_share_token_meta( $token, $share_meta );
			}
		}

		// Also award the original reviewer — ONLY when the purchase came through
		// the lightbox (source = review_details_modal or review_hotspot).
		// Regular add-to-cart conversions reward the sharer only.
		$source           = isset( $tracking['source'] ) ? sanitize_key( (string) $tracking['source'] ) : '';
		$lightbox_sources = array( 'review_details_modal', 'review_hotspot' );

		if ( in_array( $source, $lightbox_sources, true ) && $review_id > 0 ) {
			$review      = $repository->find( $review_id );
			$reviewer_id = 0;
			if ( $review ) {
				$review_data = $review->to_array();
				$reviewer_id = absint( $review_data['user_id'] ?? 0 );
			}

			// Reviewer must be different from the sharer and not the buyer themselves.
			$reviewer_is_buyer = ( $order instanceof \WC_Order ) && $this->is_lightbox_self_purchase( $order, $reviewer_id );
			if ( $reviewer_id > 0 && $reviewer_id !== $sharer_id && ! $reviewer_is_buyer && empty( $share_meta['awarded']['reviewer_conversion'] ) ) {
				$manager = new \Ugcify\Points\PointsManager();
				$result  = $manager->credit_with_reference_key(
					$reviewer_id,
					'pts_share_conversion',
					__( 'Social share purchase completed for your review', 'ugcify' ),
					$review_id,
					'social_share_reviewer_conversion',
					sprintf( 'social_share_reviewer_conversion:pts_share_conversion:%s', $token ),
					array( 'token' => $token )
				);
				if ( ! empty( $result ) ) {
					$share_meta['awarded']['reviewer_conversion'] = true;
					$repository->update_share_token_meta( $token, $share_meta );
				}
			}
		}
	}

	/**
	 * Award share conversion points for tokens attached to completed order items.
	 *
	 * @param \WC_Order $order Order object.
	 * @return void
	 */
	private function maybe_award_share_conversion_points_from_order( $order ) {
		if ( ! $order instanceof \WC_Order ) {
			return;
		}

		foreach ( $order->get_items( 'line_item' ) as $item ) {
			if ( ! $item instanceof \WC_Order_Item_Product ) {
				continue;
			}

			$tracking = $item->get_meta( self::ORDER_ITEM_TRACKING_META_KEY, true );

			if ( is_array( $tracking ) && ! empty( $tracking['ugc_share'] ) ) {
				// Lightbox-attributed item — source is already in tracking payload.
				$this->maybe_award_share_token_points( $tracking, 'conversion', $order );
				continue;
			}

			// Regular add-to-cart: share token may have been stored directly on the
			// item meta (set by add_tracking_meta_to_order_line_item via cart session).
			$token = sanitize_key( (string) ( $item->get_meta( '_ugcify_share_token', true ) ?? '' ) );
			if ( '' === $token ) {
				continue;
			}

			// Build a minimal tracking payload with no lightbox source so the
			// reviewer does NOT get points — only the sharer does.
			$self_click = '1' === (string) $item->get_meta( '_ugcify_share_self_click', true );
			$this->maybe_award_share_token_points(
				array(
					'ugc_share'  => $token,
					'source'     => 'regular_add_to_cart',
					'self_click' => $self_click,
				),
				'conversion',
				$order
			);
		}
	}

	/**
	 * Determine whether a lightbox purchase reward would be self-awarded.
	 *
	 * @param \WC_Order $order Order object.
	 * @param int       $reviewer_user_id Reviewer user ID.
	 * @return bool
	 */
	private function is_lightbox_self_purchase( $order, $reviewer_user_id ) {
		$reviewer_user_id = absint( $reviewer_user_id );
		if ( $reviewer_user_id <= 0 ) {
			return false;
		}

		$order_customer_id = method_exists( $order, 'get_customer_id' ) ? absint( $order->get_customer_id() ) : 0;
		if ( $order_customer_id > 0 && $order_customer_id === $reviewer_user_id ) {
			return true;
		}

		if ( ! function_exists( 'get_userdata' ) || ! method_exists( $order, 'get_billing_email' ) ) {
			return false;
		}

		$reviewer = get_userdata( $reviewer_user_id );
		if ( ! $reviewer || empty( $reviewer->user_email ) ) {
			return false;
		}

		$order_email    = strtolower( trim( (string) $order->get_billing_email() ) );
		$reviewer_email = strtolower( trim( (string) $reviewer->user_email ) );

		return '' !== $order_email && $order_email === $reviewer_email;
	}

	/**
	 * Increment AB metric by test payload.
	 *
	 * @param array  $test Test configuration.
	 * @param string $variant Variant ('original' or 'ugcify').
	 * @param string $metric Metric key.
	 * @return void
	 */
	private function increment_ab_metric( array $test, $variant, $metric ) {
		$analytics_storage = get_option(
			self::AB_ANALYTICS_OPTION_NAME,
			array( 'tests' => array() )
		);

		if ( ! is_array( $analytics_storage ) ) {
			$analytics_storage = array( 'tests' => array() );
		}

		$test_id = isset( $test['id'] ) ? sanitize_key( (string) $test['id'] ) : '';
		if ( '' === $test_id ) {
			return;
		}

		if ( ! isset( $analytics_storage['tests'][ $test_id ] ) || ! is_array( $analytics_storage['tests'][ $test_id ] ) ) {
			$analytics_storage['tests'][ $test_id ] = array(
				'product_id' => isset( $test['product_id'] ) ? absint( $test['product_id'] ) : 0,
				'test_name'  => isset( $test['test_name'] ) ? sanitize_text_field( (string) $test['test_name'] ) : '',
				'test_type'  => isset( $test['test_type'] ) ? sanitize_text_field( (string) $test['test_type'] ) : '',
				'tab_key'    => isset( $test['tab_key'] ) ? sanitize_key( (string) $test['tab_key'] ) : '',
				'variants'   => array(),
				'updated_at' => '',
				'last_event' => '',
			);
		}

		if ( ! isset( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ] ) || ! is_array( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ] ) ) {
			$analytics_storage['tests'][ $test_id ]['variants'][ $variant ] = array();
		}

		$current_value = isset( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ][ $metric ] )
			? absint( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ][ $metric ] )
			: 0;

		$analytics_storage['tests'][ $test_id ]['variants'][ $variant ][ $metric ] = $current_value + 1;
		$analytics_storage['tests'][ $test_id ]['updated_at']                      = current_time( 'mysql', true );
		$analytics_storage['tests'][ $test_id ]['last_event']                      = sanitize_key( (string) $metric );

		update_option( self::AB_ANALYTICS_OPTION_NAME, $analytics_storage );
	}

	/**
	 * Increment AB metric by product ID if an active running image/gallery test exists.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $variant Variant key.
	 * @param string $metric Metric key.
	 * @return void
	 */
	private function increment_ab_metric_for_product( $product_id, $variant, $metric ) {
		$product_id = absint( $product_id );
		if ( $product_id < 1 ) {
			return;
		}

		$storage = get_option(
			self::AB_TESTS_OPTION_NAME,
			array( 'items' => array() )
		);

		$items = isset( $storage['items'] ) && is_array( $storage['items'] ) ? $storage['items'] : array();

		foreach ( $items as $test ) {
			if ( ! is_array( $test ) ) {
				continue;
			}

			$test_product_id = isset( $test['product_id'] ) ? absint( $test['product_id'] ) : 0;
			$status          = isset( $test['status'] ) ? sanitize_key( (string) $test['status'] ) : '';
			$tab_key         = isset( $test['tab_key'] ) ? sanitize_key( (string) $test['tab_key'] ) : '';
			$test_type       = isset( $test['test_type'] ) ? strtolower( sanitize_text_field( (string) $test['test_type'] ) ) : '';
			$is_supported_test = in_array( $tab_key, array( 'image', 'gallery', 'ugcify-position' ), true ) || in_array( $test_type, array( 'image', 'layout', 'position' ), true );

			if ( $test_product_id !== $product_id || 'running' !== $status || ! $is_supported_test ) {
				continue;
			}

			$this->increment_ab_metric( $test, $variant, $metric );
			break;
		}
	}

	/**
	 * Increment a float AB metric (for revenue-like values).
	 *
	 * @param array  $test Test configuration.
	 * @param string $variant Variant ('original' or 'ugcify').
	 * @param string $metric Metric key.
	 * @param float  $amount Amount to add.
	 * @return void
	 */
	private function increment_ab_revenue_metric( array $test, $variant, $metric, $amount ) {
		$amount = (float) $amount;
		if ( $amount <= 0 ) {
			return;
		}

		$analytics_storage = get_option(
			self::AB_ANALYTICS_OPTION_NAME,
			array( 'tests' => array() )
		);

		if ( ! is_array( $analytics_storage ) ) {
			$analytics_storage = array( 'tests' => array() );
		}

		$test_id = isset( $test['id'] ) ? sanitize_key( (string) $test['id'] ) : '';
		if ( '' === $test_id ) {
			return;
		}

		if ( ! isset( $analytics_storage['tests'][ $test_id ] ) || ! is_array( $analytics_storage['tests'][ $test_id ] ) ) {
			$analytics_storage['tests'][ $test_id ] = array(
				'product_id' => isset( $test['product_id'] ) ? absint( $test['product_id'] ) : 0,
				'test_name'  => isset( $test['test_name'] ) ? sanitize_text_field( (string) $test['test_name'] ) : '',
				'test_type'  => isset( $test['test_type'] ) ? sanitize_text_field( (string) $test['test_type'] ) : '',
				'tab_key'    => isset( $test['tab_key'] ) ? sanitize_key( (string) $test['tab_key'] ) : '',
				'variants'   => array(),
				'updated_at' => '',
				'last_event' => '',
			);
		}

		if ( ! isset( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ] ) || ! is_array( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ] ) ) {
			$analytics_storage['tests'][ $test_id ]['variants'][ $variant ] = array();
		}

		$current_value = isset( $analytics_storage['tests'][ $test_id ]['variants'][ $variant ][ $metric ] )
			? (float) $analytics_storage['tests'][ $test_id ]['variants'][ $variant ][ $metric ]
			: 0.0;

		$analytics_storage['tests'][ $test_id ]['variants'][ $variant ][ $metric ] = (float) wc_format_decimal( $current_value + $amount, wc_get_price_decimals() );
		$analytics_storage['tests'][ $test_id ]['updated_at']                      = current_time( 'mysql', true );
		$analytics_storage['tests'][ $test_id ]['last_event']                      = sanitize_key( (string) $metric );

		update_option( self::AB_ANALYTICS_OPTION_NAME, $analytics_storage );
	}

	/**
	 * Normalize A/B tracking payload shape and strip unsafe values.
	 *
	 * @param mixed $tracking Raw tracking payload.
	 * @return array
	 */
	private function sanitize_ab_tracking( $tracking ) {
		if ( ! is_array( $tracking ) ) {
			return array();
		}

		$test_id = isset( $tracking['test_id'] ) ? sanitize_key( (string) $tracking['test_id'] ) : '';
		$product_id = isset( $tracking['product_id'] ) ? absint( $tracking['product_id'] ) : 0;
		$variant = isset( $tracking['variant'] ) ? sanitize_key( (string) $tracking['variant'] ) : 'original';
		$source = isset( $tracking['source'] ) ? sanitize_key( (string) $tracking['source'] ) : 'product';
		$image_id = isset( $tracking['image_id'] ) ? absint( $tracking['image_id'] ) : 0;

		if ( '' === $test_id || $product_id < 1 ) {
			return array();
		}

		if ( ! in_array( $variant, array( 'original', 'ugcify' ), true ) ) {
			$variant = 'original';
		}

		return array(
			'test_id'    => $test_id,
			'product_id' => $product_id,
			'variant'    => $variant,
			'source'     => $source,
			'image_id'   => $image_id,
		);
	}

	/**
	 * Build normalized lightbox request payload from add-to-cart request.
	 *
	 * @return array
	 */
	private function get_lightbox_tracking_request_data() {
		if ( empty( $_REQUEST['ugcify_lightbox_source'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified upstream by WooCommerce add-to-cart nonce.
			return array();
		}

		$source = sanitize_key( wp_unslash( $_REQUEST['ugcify_lightbox_source'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( empty( $source ) ) {
			return array();
		}

		$allowed_sources = (array) apply_filters( 'ugcify_lightbox_tracking_sources', array( 'review_details_modal', 'review_hotspot' ) );
		if ( ! in_array( $source, $allowed_sources, true ) ) {
			return array();
		}

		$review_id = $this->get_request_positive_int( 'ugcify_lightbox_review_id' );
		$ugcify_id = $this->get_request_positive_int( 'ugcify_lightbox_ugcify_id' );
		$ugc_share = isset( $_REQUEST['ugc_share'] ) ? sanitize_key( wp_unslash( $_REQUEST['ugc_share'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		// If review_id is missing but we have a share token, resolve it from the token.
		if ( ( $review_id <= 0 || $ugcify_id <= 0 ) && '' !== $ugc_share && class_exists( '\\Ugcify\\Database\\ReviewRepository' ) ) {
			$repository = new \Ugcify\Database\ReviewRepository();
			$share_meta = $repository->find_by_share_token( $ugc_share );
			if ( is_array( $share_meta ) ) {
				$token_review_id = absint( $share_meta['review_id'] ?? 0 );
				if ( $token_review_id > 0 ) {
					if ( $review_id <= 0 ) {
						$review_id = $token_review_id;
					}
					if ( $ugcify_id <= 0 ) {
						$ugcify_id = $token_review_id;
					}
				}
			}
		}

		if ( $ugcify_id <= 0 ) {
			$ugcify_id = $review_id;
		}

		if ( $review_id <= 0 && $ugcify_id <= 0 ) {
			return array();
		}

		return array(
			'source'     => $source,
			'review_id'  => $review_id,
			'ugcify_id'  => $ugcify_id,
			'ugc_share'  => $ugc_share,
			'self_click' => ! empty( $_REQUEST['ugcify_share_self_click'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		);
	}

	/**
	 * Read and sanitize a positive integer from current request payload.
	 *
	 * @param string $key Request key.
	 * @return int
	 */
	private function get_request_positive_int( $key ) {
		if ( empty( $_REQUEST[ $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified upstream by WooCommerce add-to-cart nonce.
			return 0;
		}

		return absint( wp_unslash( $_REQUEST[ $key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	}

	/**
	 * Merge one add-to-cart lightbox attribution event into existing cart tracking.
	 *
	 * @param array $existing_tracking Existing tracking payload.
	 * @param array $incoming_tracking Incoming request tracking payload.
	 * @param int   $quantity Added quantity.
	 * @param int   $product_id Product ID.
	 * @param int   $variation_id Variation ID.
	 * @return array
	 */
	private function merge_cart_tracking( $existing_tracking, $incoming_tracking, $quantity, $product_id, $variation_id ) {
		$quantity = max( 1, absint( $quantity ) );
		$tracking = $this->sanitize_cart_tracking( $existing_tracking );

		if ( empty( $tracking ) ) {
			$tracking = array(
				'source_flag'      => 'ugcify_lightbox',
				'source'           => sanitize_key( $incoming_tracking['source'] ),
				'product_id'       => absint( $product_id ),
				'variation_id'     => absint( $variation_id ),
				'tracked_quantity' => 0,
				'attributions'     => array(),
				'review_ids'       => array(),
				'ugcify_ids'          => array(),
			);
		}

		$tracking['source_flag']      = 'ugcify_lightbox';
		$tracking['source']           = sanitize_key( $incoming_tracking['source'] );
		$tracking['product_id']       = absint( $product_id );
		$tracking['variation_id']     = absint( $variation_id );
		$tracking['tracked_quantity'] = absint( $tracking['tracked_quantity'] ) + $quantity;
		$tracking['ugc_share']        = isset( $incoming_tracking['ugc_share'] ) ? sanitize_key( (string) $incoming_tracking['ugc_share'] ) : '';

		$incoming_review_id = absint( $incoming_tracking['review_id'] );
		$incoming_ugcify_id    = absint( $incoming_tracking['ugcify_id'] );

		if ( $incoming_ugcify_id <= 0 ) {
			$incoming_ugcify_id = $incoming_review_id;
		}

		if ( $incoming_review_id > 0 || $incoming_ugcify_id > 0 ) {
			$found = false;

			foreach ( $tracking['attributions'] as &$attribution ) {
				$review_id = isset( $attribution['review_id'] ) ? absint( $attribution['review_id'] ) : 0;
				$ugcify_id    = isset( $attribution['ugcify_id'] ) ? absint( $attribution['ugcify_id'] ) : 0;

				if ( $review_id === $incoming_review_id && $ugcify_id === $incoming_ugcify_id ) {
					$attribution['quantity'] = absint( $attribution['quantity'] ) + $quantity;
					$found                   = true;
					break;
				}
			}
			unset( $attribution );

			if ( ! $found ) {
				$tracking['attributions'][] = array(
					'review_id' => $incoming_review_id,
					'ugcify_id'    => $incoming_ugcify_id,
					'quantity'  => $quantity,
				);
			}
		}

		return $this->sanitize_cart_tracking( $tracking );
	}

	/**
	 * Normalize tracking payload shape and strip unsafe values.
	 *
	 * @param mixed $tracking Raw tracking payload.
	 * @return array
	 */
	private function sanitize_cart_tracking( $tracking ) {
		if ( ! is_array( $tracking ) ) {
			return array();
		}

		$sanitized = array(
			'source_flag'      => 'ugcify_lightbox',
			'source'           => ! empty( $tracking['source'] ) ? sanitize_key( $tracking['source'] ) : 'review_details_modal',
			'product_id'       => isset( $tracking['product_id'] ) ? absint( $tracking['product_id'] ) : 0,
			'variation_id'     => isset( $tracking['variation_id'] ) ? absint( $tracking['variation_id'] ) : 0,
			'ab_variant'       => isset( $tracking['ab_variant'] ) ? sanitize_key( (string) $tracking['ab_variant'] ) : 'original',
			'ab_test_id'       => isset( $tracking['ab_test_id'] ) ? sanitize_key( (string) $tracking['ab_test_id'] ) : '',
			'tracked_quantity' => isset( $tracking['tracked_quantity'] ) ? absint( $tracking['tracked_quantity'] ) : 0,
			'ugc_share'        => isset( $tracking['ugc_share'] ) ? sanitize_key( (string) $tracking['ugc_share'] ) : '',
			'attributions'     => array(),
			'review_ids'       => array(),
			'ugcify_ids'          => array(),
		);

		if ( ! in_array( $sanitized['ab_variant'], array( 'original', 'ugcify' ), true ) ) {
			$sanitized['ab_variant'] = 'original';
		}

		$attributions = isset( $tracking['attributions'] ) && is_array( $tracking['attributions'] ) ? $tracking['attributions'] : array();

		foreach ( $attributions as $attribution ) {
			if ( ! is_array( $attribution ) ) {
				continue;
			}

			$review_id = isset( $attribution['review_id'] ) ? absint( $attribution['review_id'] ) : 0;
			$ugcify_id    = isset( $attribution['ugcify_id'] ) ? absint( $attribution['ugcify_id'] ) : 0;
			$quantity  = isset( $attribution['quantity'] ) ? absint( $attribution['quantity'] ) : 0;

			if ( $ugcify_id <= 0 ) {
				$ugcify_id = $review_id;
			}

			if ( $quantity <= 0 || ( $review_id <= 0 && $ugcify_id <= 0 ) ) {
				continue;
			}

			$sanitized['attributions'][] = array(
				'review_id' => $review_id,
				'ugcify_id'    => $ugcify_id,
				'quantity'  => $quantity,
			);
		}

		// Backward compatible fallback for single attribution payloads.
		if ( empty( $sanitized['attributions'] ) ) {
			$review_id = isset( $tracking['review_id'] ) ? absint( $tracking['review_id'] ) : 0;
			$ugcify_id    = isset( $tracking['ugcify_id'] ) ? absint( $tracking['ugcify_id'] ) : 0;

			if ( $ugcify_id <= 0 ) {
				$ugcify_id = $review_id;
			}

			if ( $review_id > 0 || $ugcify_id > 0 ) {
				$fallback_quantity = max( 1, absint( $sanitized['tracked_quantity'] ) );
				$sanitized['attributions'][] = array(
					'review_id' => $review_id,
					'ugcify_id'    => $ugcify_id,
					'quantity'  => $fallback_quantity,
				);
			}
		}

		if ( empty( $sanitized['attributions'] ) ) {
			return array();
		}

		$merged_attributions = array();
		foreach ( $sanitized['attributions'] as $attribution ) {
			$merge_key = $attribution['review_id'] . ':' . $attribution['ugcify_id'];
			if ( ! isset( $merged_attributions[ $merge_key ] ) ) {
				$merged_attributions[ $merge_key ] = $attribution;
				continue;
			}

			$merged_attributions[ $merge_key ]['quantity'] += absint( $attribution['quantity'] );
		}

		$sanitized['attributions'] = array_values( $merged_attributions );
		$sanitized['review_ids']   = $this->unique_positive_ints( wp_list_pluck( $sanitized['attributions'], 'review_id' ) );
		$sanitized['ugcify_ids']      = $this->unique_positive_ints( wp_list_pluck( $sanitized['attributions'], 'ugcify_id' ) );

		$sanitized['tracked_quantity'] = 0;
		foreach ( $sanitized['attributions'] as $attribution ) {
			$sanitized['tracked_quantity'] += absint( $attribution['quantity'] );
		}

		return $sanitized;
	}

	/**
	 * Clamp tracking quantity so it can never exceed current line quantity.
	 *
	 * @param mixed $tracking Raw tracking payload.
	 * @param int   $line_quantity Current line quantity.
	 * @return array
	 */
	private function normalize_tracking_for_line_item( $tracking, $line_quantity ) {
		$line_quantity = max( 0, absint( $line_quantity ) );
		if ( $line_quantity <= 0 ) {
			return array();
		}

		$tracking = $this->sanitize_cart_tracking( $tracking );
		if ( empty( $tracking ) ) {
			return array();
		}

		$current_tracked_quantity = absint( $tracking['tracked_quantity'] );
		if ( $current_tracked_quantity === $line_quantity ) {
			return $tracking;
		}

		// If quantity increased after lightbox add-to-cart, preserve attribution
		// and assign the delta to the first attribution entry.
		if ( $current_tracked_quantity < $line_quantity ) {
			$delta = $line_quantity - $current_tracked_quantity;
			if ( ! empty( $tracking['attributions'][0] ) ) {
				$tracking['attributions'][0]['quantity'] = absint( $tracking['attributions'][0]['quantity'] ) + $delta;
			}

			$tracking['tracked_quantity'] = $line_quantity;
			$tracking['review_ids']       = $this->unique_positive_ints( wp_list_pluck( $tracking['attributions'], 'review_id' ) );
			$tracking['ugcify_ids']          = $this->unique_positive_ints( wp_list_pluck( $tracking['attributions'], 'ugcify_id' ) );

			return $tracking;
		}

		$remaining            = $line_quantity;
		$trimmed_attributions = array();

		foreach ( $tracking['attributions'] as $attribution ) {
			if ( $remaining <= 0 ) {
				break;
			}

			$quantity = min( $remaining, absint( $attribution['quantity'] ) );
			if ( $quantity <= 0 ) {
				continue;
			}

			$attribution['quantity'] = $quantity;
			$trimmed_attributions[]  = $attribution;
			$remaining              -= $quantity;
		}

		$tracking['tracked_quantity'] = $line_quantity;
		$tracking['attributions']     = $trimmed_attributions;
		$tracking['review_ids']       = $this->unique_positive_ints( wp_list_pluck( $trimmed_attributions, 'review_id' ) );
		$tracking['ugcify_ids']          = $this->unique_positive_ints( wp_list_pluck( $trimmed_attributions, 'ugcify_id' ) );

		return $tracking;
	}

	/**
	 * Remove stale lightbox order meta from orders that no longer contain tracked lines.
	 *
	 * @param \WC_Order $order Order object.
	 * @return void
	 */
	private function clear_order_lightbox_tracking_meta( $order ) {
		$order->delete_meta_data( self::ORDER_META_HAS_SALE_KEY );
		$order->delete_meta_data( self::ORDER_META_SUMMARY_KEY );
		$order->delete_meta_data( self::ORDER_META_REVENUE_KEY );
		$order->delete_meta_data( self::ORDER_META_REVIEW_IDS_KEY );
		$order->delete_meta_data( self::ORDER_META_UGC_IDS_KEY );
		$order->delete_meta_data( self::ORDER_META_TRACKED_QUANTITY_KEY );
		$order->delete_meta_data( self::ORDER_META_ITEMS_COUNT_KEY );
		$order->delete_meta_data( self::ORDER_META_REWARDED_REVIEW_IDS_KEY );
		$order->save();
	}

	/**
	 * Return unique positive integers while preserving order.
	 *
	 * @param array $ids IDs list.
	 * @return array
	 */
	private function unique_positive_ints( $ids ) {
		$unique_ids = array();

		foreach ( (array) $ids as $id ) {
			$int_id = absint( $id );
			if ( $int_id <= 0 || in_array( $int_id, $unique_ids, true ) ) {
				continue;
			}

			$unique_ids[] = $int_id;
		}

		return $unique_ids;
	}

	/**
	 * Add review button after add to cart button
	 *
	 * @return void
	 */
	public function add_review_button() {
		global $product;

		if ( ! $product ) {
			return;
		}

		echo '<div class="ugcify-review-button-wrapper">';
		echo wp_kses_post( $this->form_renderer->render(
			array(
				'product_id' => $product->get_id(),
				'type'       => 'button',
			)
		) );
		echo '</div>';
	}

	/**
	 * Remove WooCommerce review blocks in FSE/block themes.
	 *
	 * FSE themes use WooCommerce Gutenberg blocks (e.g. woocommerce/product-reviews)
	 * instead of the PHP-based woocommerce_product_tabs filter. This causes duplicate
	 * reviews since the plugin renders its own review section via the tabs.
	 *
	 * @param string $block_content The block content.
	 * @param array  $block         The full block, including name and attributes.
	 * @return string
	 */
	public function remove_wc_review_blocks( $block_content, $block ) {
		if ( ! is_product() ) {
			return $block_content;
		}

		// Hide the accordion item that wraps WooCommerce review blocks.
		if ( 'woocommerce/accordion-item' === $block['blockName'] && $this->block_contains_reviews( $block ) ) {
			return '';
		}

		return $block_content;
	}

	/**
	 * Check if a block or its inner blocks contain a WooCommerce product-reviews block.
	 *
	 * @param array $block The block to check.
	 * @return bool
	 */
	private function block_contains_reviews( $block ) {
		if ( 'woocommerce/product-reviews' === $block['blockName'] ) {
			return true;
		}

		if ( ! empty( $block['innerBlocks'] ) ) {
			foreach ( $block['innerBlocks'] as $inner_block ) {
				if ( $this->block_contains_reviews( $inner_block ) ) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Replace comments template with our custom review template
	 *
	 * @param string $template Template path.
	 * @return string
	 */
	public function replace_comments_template( $template ) {
		global $post;

		if ( ! $post || 'product' !== get_post_type( $post ) ) {
			return $template;
		}

		// Use our custom template if it exists.
		$custom_template = UGCIFY_PATH . 'templates/reviews-wrapper.php';

		if ( file_exists( $custom_template ) ) {
			return $custom_template;
		}

		return $template;
	}

	/**
	 * Get template customization settings
	 *
	 * @return array
	 */
	private function get_template_customization_settings() {
		$defaults = array(
			// General.
			'enableWidget'            => false,
			'widgetType'              => 'grid',

			// Header Area.
			'showHeaderTitle'         => true,
			'title'                   => 'Customers are saying',
			'titleFontWeight'         => '600',
			'titleFontSize'           => 'medium',
			'emptyReviewText'         => 'No customer review yet.',

			// Colors and Styles.
			'primaryColor'            => '#201CFE',
			'titleColor'              => '#000000',
			'bodyFontColor'           => '#444444',
			'cardBgColor'             => '#FFFFFF',
			'ratingColor'             => '#FFA800',
			'buttonTextColor'         => '#FFFFFF',
			'filterFillColor'         => '#FFFFFF',
			'filterStrokeColor'       => '#201CFE',
			'filterIconColor'         => '#292D32',
			'readMoreColor'           => '#000000',

			// Review Section.
			'reviewerNameFormat'      => 'full',
			'reviewerNameFontSize'    => 'medium',
			'showVerifiedCheck'       => true,
			'showInfluencerBadge'     => true,
			'reviewDateFormat'        => 'd M, Y',
			'starRatingSize'          => 'medium',
			'reviewTextSize'          => 'medium',
			'reviewTextLength'        => 'large',
			'reviewCardBorderRadius'  => '10',
			'reviewColumn'            => '3',
			'reviewsPerPage'          => '3',
			'paginationType'          => 'page_number',

			// Advanced.
			'customCSS'               => '',
			'shortcode'               => '[ugcify_reviews product_id="123"]',
		);

		$saved = get_option( 'ugcify_template_customization', array() );
		if ( is_array( $saved ) ) {
			unset( $saved['tagStyle'] );
		}

		if ( is_array( $saved ) && ! empty( $saved ) ) {
			$settings = array_merge( $defaults, $saved );
		} else {
			$settings = $defaults;
		}

		$gallery_settings = get_option( 'ugcify_review_template_settings', array() );
		$active_id        = isset( $gallery_settings['activeTemplateId'] ) ? $gallery_settings['activeTemplateId'] : '';

		// Template selection can force dedicated widget types.
		// The grid template keeps using the saved sub-style (grid, masonry, slider, list).
		if ( 'reels' === $active_id ) {
			$settings['widgetType'] = 'reels';
		} elseif ( 'gallery' === $active_id ) {
			$settings['widgetType'] = 'gallery';
		} elseif ( 'gallery' === $settings['widgetType'] || 'reels' === $settings['widgetType'] ) {
			$settings['widgetType'] = 'masonry' === ( $settings['gridStyle'] ?? '' ) ? 'masonry' : 'grid';
		}

		if ( 'reels' === $settings['widgetType'] ) {
			$settings = array_merge( $settings, $this->get_reels_settings() );
		}

		return $settings;
	}

	/**
	 * Get merged reels customization settings.
	 *
	 * @return array
	 */
	private function get_reels_settings() {
		$defaults = array(
			'showStories'        => true,
			'storiesTitle'       => 'Customer Stories',
			'storiesMaxCount'    => 8,
			'showAddYours'       => true,
			'showCommunity'      => true,
			'communityTitle'     => 'Community Reviews',
			'communitySubtitle'  => 'See what our customers are saying',
			'showShareBtn'       => true,
			'reviewerNameFormat' => 'first_last_initial',
			'reviewDateFormat'   => 'd M, Y',
			'showVerifiedCheck'  => true,
			'reviewsPerPage'     => '12',
			'primaryColor'       => '#201CFE',
			'ratingColor'        => '#FFA800',
			'customCSS'          => '',
		);

		$saved = get_option( 'ugcify_reels_settings', array() );
		if ( is_array( $saved ) && ! empty( $saved ) ) {
			return array_merge( $defaults, $saved );
		}

		return $defaults;
	}

	/**
	 * Generate dynamic CSS for template customization
	 *
	 * @param array $settings Template customization settings.
	 * @return string
	 */
	private function generate_template_css( $settings ) {
		$css = '';

		// Font sizes mapping for titles.
		$title_font_sizes = array(
			'small'  => '20px',
			'medium' => '24px',
			'large'  => '28px',
		);

		// Font sizes mapping for text content.
		$text_font_sizes = array(
			'small'  => '14px',
			'medium' => '16px',
			'large'  => '18px',
		);

		// Title styles (includes title, stats label, author name).
		if ( ! empty( $settings['titleColor'] ) ) {
			$css .= '.ugcify-reviews-section .ugcify-main-title { color: ' . esc_attr( $settings['titleColor'] ) . ' !important; }';
			$css .= '.ugcify-reviews-section .ugcify-stats-label, .ugcify-review-card .ugcify-author-name { color: ' . esc_attr( $settings['titleColor'] ) . ' !important; }';
		}
		if ( ! empty( $settings['titleFontWeight'] ) ) {
			$css .= '.ugcify-reviews-section .ugcify-main-title { font-weight: ' . esc_attr( $settings['titleFontWeight'] ) . ' !important; }';
		}
		if ( ! empty( $settings['titleFontSize'] ) && isset( $title_font_sizes[ $settings['titleFontSize'] ] ) ) {
			$css .= '.ugcify-reviews-section .ugcify-main-title { font-size: ' . esc_attr( $title_font_sizes[ $settings['titleFontSize'] ] ) . ' !important; }';
		}

		// Primary color (buttons, links, pagination).
		if ( ! empty( $settings['primaryColor'] ) ) {
			$css .= '.ugcify-write-review-btn { background-color: ' . esc_attr( $settings['primaryColor'] ) . ' !important; border-color: ' . esc_attr( $settings['primaryColor'] ) . ' !important; }';
			$css .= '.ugcify-filter-btn { border-color: ' . esc_attr( $settings['filterStrokeColor'] ) . ' !important; }';
			$css .= '.ugcify-pagination .page-numbers:not(.disabled):hover, .ugcify-pagination .page-numbers.current { border-color: ' . esc_attr( $settings['primaryColor'] ) . ' !important; color: ' . esc_attr( $settings['primaryColor'] ) . ' !important; }';
		}

		// Button text color.
		if ( ! empty( $settings['buttonTextColor'] ) ) {
			$css .= '.ugcify-write-review-btn { color: ' . esc_attr( $settings['buttonTextColor'] ) . ' !important; }';
		}

		// Body font color (includes text, date, counts, pagination info).
		if ( ! empty( $settings['bodyFontColor'] ) ) {
			$css .= '.ugcify-review-card .ugcify-card-text, .ugcify-review-card .ugcify-card-date { color: ' . esc_attr( $settings['bodyFontColor'] ) . ' !important; }';
			$css .= '.ugcify-reviews-section .ugcify-total-count, .ugcify-pagination-info, .ugcify-pagination .page-status { color: ' . esc_attr( $settings['bodyFontColor'] ) . ' !important; }';
		}

		// Card background color.
		if ( ! empty( $settings['cardBgColor'] ) ) {
			$css .= '.ugcify-review-card { background-color: ' . esc_attr( $settings['cardBgColor'] ) . ' !important; }';
		}

		// Rating color (stars).
		if ( ! empty( $settings['ratingColor'] ) ) {
			$rating_color = esc_attr( $settings['ratingColor'] );
			$css         .= '.ugcify-review-card .ugcify-card-rating .ugcify-star-display.full, .ugcify-stars-summary .ugcify-star-display.full, .ugcify-stars-display .ugcify-star-display.full { background-color: ' . $rating_color . ' !important; }';
			$css         .= '.ugcify-review-card .ugcify-card-rating .ugcify-star-display.half, .ugcify-stars-summary .ugcify-star-display.half, .ugcify-stars-display .ugcify-star-display.half { background: linear-gradient(90deg, ' . $rating_color . ' 50%, #e0e0e0 50%) !important; }';
		}

		// Read more color.
		if ( ! empty( $settings['readMoreColor'] ) ) {
			$css .= '.ugcify-review-card .ugcify-read-more { color: ' . esc_attr( $settings['readMoreColor'] ) . ' !important; }';
		}

		// Filter colors.
		if ( ! empty( $settings['filterFillColor'] ) ) {
			$css .= '.ugcify-filter-btn { background-color: ' . esc_attr( $settings['filterFillColor'] ) . ' !important; }';
		}
		if ( ! empty( $settings['filterIconColor'] ) ) {
			$css .= '.ugcify-filter-btn svg path { stroke: ' . esc_attr( $settings['filterIconColor'] ) . ' !important; }';
		}

		// Review card border radius.
		if ( ! empty( $settings['reviewCardBorderRadius'] ) ) {
			$css .= '.ugcify-review-card { border-radius: ' . esc_attr( $settings['reviewCardBorderRadius'] ) . 'px !important; }';
		}

		// Grid/Masonry columns and layout.
		if ( ! empty( $settings['reviewColumn'] ) ) {
			$columns        = max( 1, min( 6, intval( $settings['reviewColumn'] ) ) );
			$tablet_columns = min( 2, $columns );
			$widget_type = ! empty( $settings['widgetType'] ) ? $settings['widgetType'] : 'grid';
			
			if ( 'masonry' === $widget_type ) {
				$css .= '.ugcify-reviews-grid.ugcify-layout-masonry { column-count: ' . esc_attr( $columns ) . ' !important; display: block !important; }';
				$css .= '@media (max-width: 768px) {.ugcify-reviews-grid.ugcify-layout-masonry { column-count: ' . esc_attr( $tablet_columns ) . ' !important; }}';
				$css .= '@media (max-width: 640px) {.ugcify-reviews-grid.ugcify-layout-masonry { column-count: 1 !important; }}';
				$css .= '@container (max-width: 640px) {.ugcify-reviews-grid.ugcify-layout-masonry { column-count: 1 !important; }}';
				$css .= '.ugcify-reviews-grid.ugcify-layout-masonry .ugcify-review-card { break-inside: avoid !important; margin-bottom: 30px !important; display: inline-block !important; width: 100% !important; }';
			} elseif ( 'list' === $widget_type ) {
				$css .= '.ugcify-reviews-grid.ugcify-layout-list { display: flex !important; flex-direction: column !important; gap: 20px !important; }';
				$css .= '.ugcify-reviews-grid.ugcify-layout-list .ugcify-review-card { width: 100% !important; }';
			} else {
				$css .= '.ugcify-reviews-grid { grid-template-columns: repeat(' . esc_attr( $columns ) . ', 1fr) !important; }';
				$css .= '@media (max-width: 768px) {.ugcify-reviews-grid { grid-template-columns: repeat(' . esc_attr( $tablet_columns ) . ', 1fr) !important; }}';
				$css .= '@media (max-width: 640px) {.ugcify-reviews-grid { grid-template-columns: 1fr !important; }}';
				$css .= '@container (max-width: 640px) {.ugcify-reviews-grid { grid-template-columns: 1fr !important; }}';
			}
		}

		// Reviewer name font size.
		if ( ! empty( $settings['reviewerNameFontSize'] ) && isset( $font_sizes[ $settings['reviewerNameFontSize'] ] ) ) {
			$css .= '.ugcify-review-card .ugcify-author-name { font-size: ' . esc_attr( $font_sizes[ $settings['reviewerNameFontSize'] ] ) . ' !important; }';
		}

		// Review text size.
		if ( ! empty( $settings['reviewTextSize'] ) && isset( $text_font_sizes[ $settings['reviewTextSize'] ] ) ) {
			$css .= '.ugcify-review-card .ugcify-card-text { font-size: ' . esc_attr( $text_font_sizes[ $settings['reviewTextSize'] ] ) . ' !important; }';
		}

		// Star rating size (applies to all star displays).
		if ( ! empty( $settings['starRatingSize'] ) ) {
			$star_sizes = array(
				'small'  => '14px',
				'medium' => '16px',
				'large'  => '18px',
			);
			if ( isset( $star_sizes[ $settings['starRatingSize'] ] ) ) {
				$star_size = esc_attr( $star_sizes[ $settings['starRatingSize'] ] );
				$css      .= '.ugcify-review-card .ugcify-card-rating .ugcify-star-display, .ugcify-stars-summary .ugcify-star-display, .ugcify-stars-display .ugcify-star-display { width: ' . $star_size . ' !important; height: ' . $star_size . ' !important; }';
			}
		}

		// Custom CSS.
		if ( ! empty( $settings['customCSS'] ) ) {
			$css .= wp_strip_all_tags( $settings['customCSS'] );
		}

		return $css;
	}

	/**
	 * Render custom reviews section content
	 *
	 * @return void
	 */
	public function render_custom_reviews_section() {
		global $product;
		if ( ! $product ) {
			return;
		}

		$product_id = $product->get_id();

		// Get template customization settings.
		$template_settings = $this->get_template_customization_settings();
		$default_widget_type  = ! empty( $template_settings['widgetType'] ) ? (string) $template_settings['widgetType'] : 'grid';
		$resolved_widget_type = $this->ab_variant_runtime->resolve_widget_type( $product_id, $default_widget_type );
		$template_settings['widgetType'] = $resolved_widget_type;

		// Output dynamic CSS via WordPress inline style API.
		$dynamic_css = $this->generate_template_css( $template_settings );
		if ( $dynamic_css ) {
			wp_register_style( 'ugcify-template-styles', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters
			wp_enqueue_style( 'ugcify-template-styles' );
			wp_add_inline_style( 'ugcify-template-styles', $dynamic_css );
		}

		$template = UGCIFY_PATH . 'templates/reviews-listing.php';

		if ( file_exists( $template ) ) {
			// Pass variables to the template.
			$form_renderer = $this->form_renderer;
			include $template;
		} else {
			echo '<div class="ugcify-custom-reviews-section">';
			// Fallback to internal renderer if template is missing.
			echo wp_kses_post( $this->form_renderer->render(
				array(
					'product_id' => $product->get_id(),
					'type'       => 'list',
				)
			) );
			echo '</div>';
		}
	}
}
