<?php
/**
 * Checkout Redemption — Apply reward points as a discount at WooCommerce checkout.
 *
 * @package Ugcify\Integrations\WooCommerce
 */

namespace Ugcify\Integrations\WooCommerce;

use Ugcify\Admin\API\RewardSettingsAPI;
use Ugcify\Points\PointsManager;
use Ugcify\Points\TierManager;

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

class CheckoutRedemption {

	const SESSION_KEY = 'ugcify_redeem_points';

	public function __construct() {
		add_action( 'woocommerce_before_checkout_form', array( $this, 'render_checkout_coupon_widget' ), 11 );
		add_action( 'woocommerce_cart_coupon', array( $this, 'render_cart_coupon_widget' ) );
		add_action( 'woocommerce_before_cart_totals', array( $this, 'render_cart_widget_fallback' ) );

		add_action( 'woocommerce_cart_calculate_fees', array( $this, 'apply_fee' ) );

		add_action( 'wp_ajax_ugcify_apply_points', array( $this, 'ajax_apply_points' ) );
		add_action( 'wp_ajax_ugcify_remove_points', array( $this, 'ajax_remove_points' ) );
		add_action( 'wp_ajax_ugcify_refresh_points_widget', array( $this, 'ajax_refresh_widget' ) );

		add_action( 'woocommerce_checkout_order_processed', array( $this, 'deduct_points_on_order' ), 10, 3 );
		add_action( 'woocommerce_store_api_checkout_order_processed', array( $this, 'deduct_points_on_store_api_order' ), 10, 1 );
		add_action( 'woocommerce_thankyou', array( $this, 'clear_session' ) );

		add_action( 'wp_footer', array( $this, 'print_block_widget_template' ), 5 );
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_widget_assets' ) );
	}

	// ------------------------------------------------------------------
	// Badge / conversion helpers
	// ------------------------------------------------------------------

	private function resolve_user_badge( int $user_id, array $settings ): ?array {
		$approved_count = TierManager::get_approved_upload_count( $user_id );
		$badges         = $settings['badges'] ?? array();

		$active = array_filter(
			$badges,
			function ( $b ) {
				return ! empty( $b['active'] );
			}
		);

		if ( empty( $active ) ) {
			return null;
		}

		usort(
			$active,
			function ( $a, $b ) {
				return ( $b['min_contribution'] ?? 0 ) - ( $a['min_contribution'] ?? 0 );
			}
		);

		foreach ( $active as $badge ) {
			if ( $approved_count >= (int) ( $badge['min_contribution'] ?? 0 ) ) {
				return $badge;
			}
		}

		return null;
	}

	private function has_active_badges( array $settings ): bool {
		foreach ( $settings['badges'] ?? array() as $badge ) {
			if ( ! empty( $badge['active'] ) ) {
				return true;
			}
		}

		return false;
	}

	private function get_badge_unavailable_message( array $settings ): string {
		if ( $this->has_active_badges( $settings ) ) {
			return __( 'Redeem points becomes available after you unlock an active reward badge.', 'ugcify' );
		}

		return __( 'Redeem points is not available yet because no active reward badge conversion rules are configured.', 'ugcify' );
	}

	private function get_currency_symbol(): string {
		if ( ! function_exists( 'get_woocommerce_currency_symbol' ) ) {
			return '$';
		}

		return (string) call_user_func( 'get_woocommerce_currency_symbol' );
	}

	private function get_woocommerce_instance() {
		if ( ! function_exists( 'WC' ) ) {
			return null;
		}

		return call_user_func( 'WC' );
	}

	private function coupons_enabled(): bool {
		return function_exists( 'wc_coupons_enabled' ) && (bool) call_user_func( 'wc_coupons_enabled' );
	}

	private function is_cart_or_checkout_page(): bool {
		$is_cart     = function_exists( 'is_cart' ) && (bool) call_user_func( 'is_cart' );
		$is_checkout = function_exists( 'is_checkout' ) && (bool) call_user_func( 'is_checkout' );

		return $is_cart || $is_checkout;
	}

	private function get_conversion_points( array $badge ): int {
		return max( 1, (int) ( $badge['conversion_points'] ?? 100 ) );
	}

	private function get_discount_type( array $badge ): string {
		return 'discount' === ( $badge['discount_type'] ?? 'flat' ) ? 'discount' : 'flat';
	}

	private function points_to_money( int $points, array $badge, float $cart_subtotal ): float {
		$conversion_points = $this->get_conversion_points( $badge );
		$amount            = (float) ( $badge['amount'] ?? 0 );

		if ( $points <= 0 || $amount <= 0 ) {
			return 0.0;
		}

		$ratio = $points / $conversion_points;

		if ( 'discount' === $this->get_discount_type( $badge ) ) {
			if ( $cart_subtotal <= 0 ) {
				return 0.0;
			}

			return round( $cart_subtotal * ( ( $ratio * $amount ) / 100 ), 2 );
		}

		return round( $ratio * $amount, 2 );
	}

	private function get_max_points_for_cart( int $balance, int $max_redeem, float $cart_subtotal, array $settings, array $badge ): int {
		$points_limit       = min( $balance, $max_redeem );
		$conversion_points = $this->get_conversion_points( $badge );
		$amount            = (float) ( $badge['amount'] ?? 0 );
		$discount_type     = $this->get_discount_type( $badge );

		if ( $points_limit <= 0 || $cart_subtotal <= 0 || $amount <= 0 ) {
			return 0;
		}

		if ( 'discount' === $discount_type ) {
			$percent_per_point = $amount / $conversion_points;

			if ( $percent_per_point <= 0 ) {
				return 0;
			}

			$max_percent  = 100.0;
			$points_limit = min( $points_limit, (int) floor( $max_percent / $percent_per_point ) );
		} else {
			$value_per_point = $amount / $conversion_points;

			if ( $value_per_point <= 0 ) {
				return 0;
			}

			$max_discount = $cart_subtotal;
			$points_limit = min( $points_limit, (int) floor( $max_discount / $value_per_point ) );
		}

		return max( 0, $points_limit );
	}

	// ------------------------------------------------------------------
	// Calculation engine
	// ------------------------------------------------------------------

	private function calculate_redemption( int $requested_points, int $user_id, array $settings, array $badge ): array {
		$manager = new PointsManager();
		$balance = $manager->get_balance( $user_id );

		$min_cart_amount = (float) ( $settings['min_redeem_amount'] ?? 50 );
		$max_redeem      = (int) ( $settings['max_redeem_threshold'] ?? 500 );
		$cart_subtotal   = $this->get_cart_subtotal();
		$currency        = $this->get_currency_symbol();

		if ( $cart_subtotal < $min_cart_amount ) {
			return array(
				'points'   => 0,
				'discount' => 0.0,
				'message'  => sprintf(
					/* translators: %1$s: currency symbol, %2$s: minimum cart amount */
					__( 'Redeem points becomes available when your cart subtotal reaches %1$s%2$s.', 'ugcify' ),
					$currency,
					number_format_i18n( $min_cart_amount, 2 )
				),
			);
		}

		$points_limit = $this->get_max_points_for_cart( $balance, $max_redeem, $cart_subtotal, $settings, $badge );
		$points       = min( $requested_points, $points_limit );
		$discount     = $this->points_to_money( $points, $badge, $cart_subtotal );

		$discount = min( $discount, $cart_subtotal );

		if ( $discount <= 0 || $points <= 0 ) {
			return array(
				'points'   => 0,
				'discount' => 0.0,
				'max_points' => $points_limit,
				'message'  => __( 'Not enough points for a discount.', 'ugcify' ),
			);
		}

		return array(
			'points'   => $points,
			'discount' => $discount,
			'max_points' => $points_limit,
			'message'  => sprintf(
				/* translators: %1$d: number of points redeemed, %2$s: currency symbol, %3$s: discount amount */
				__( 'Redeeming %1$d points for %2$s%3$s discount.', 'ugcify' ),
				$points,
				$currency,
				number_format_i18n( $discount, 2 )
			),
		);
	}

	private function get_cart_subtotal(): float {
		$woocommerce = $this->get_woocommerce_instance();
		if ( ! $woocommerce || empty( $woocommerce->cart ) ) {
			return 0.0;
		}

		if ( method_exists( $woocommerce->cart, 'get_cart_contents_total' ) ) {
			$cart_contents_total = (float) $woocommerce->cart->get_cart_contents_total();
			if ( $cart_contents_total > 0 ) {
				return $cart_contents_total;
			}
		}

		return (float) $woocommerce->cart->get_subtotal();
	}

	public static function get_pending_redemption_for_user( int $user_id ): ?array {
		if ( $user_id <= 0 || ! function_exists( 'WC' ) ) {
			return null;
		}

		$woocommerce = call_user_func( 'WC' );
		if ( ! $woocommerce || empty( $woocommerce->session ) ) {
			return null;
		}

		$data = $woocommerce->session->get( self::SESSION_KEY );
		if ( ! is_array( $data ) ) {
			return null;
		}

		$session_user_id = (int) ( $data['user_id'] ?? 0 );
		$points          = (int) ( $data['points'] ?? 0 );
		$discount        = (float) ( $data['discount'] ?? 0 );

		if ( $session_user_id !== $user_id || $points <= 0 ) {
			return null;
		}

		return array(
			'user_id'  => $session_user_id,
			'points'   => $points,
			'discount' => max( 0, $discount ),
		);
	}

	public static function apply_pending_redemption_to_summary( array $summary, int $user_id ): array {
		$pending = self::get_pending_redemption_for_user( $user_id );
		if ( empty( $pending ) ) {
			$summary['pending_redeem_points']   = 0;
			$summary['pending_redeem_discount'] = 0.0;
			return $summary;
		}

		$pending_points                   = min( (int) $pending['points'], max( 0, (int) ( $summary['available_balance'] ?? 0 ) ) );
		$summary['available_balance']     = max( 0, (int) ( $summary['available_balance'] ?? 0 ) - $pending_points );
		$summary['total_used']            = max( 0, (int) ( $summary['total_used'] ?? 0 ) + $pending_points );
		$summary['pending_redeem_points'] = $pending_points;
		$summary['pending_redeem_discount'] = (float) ( $pending['discount'] ?? 0 );

		return $summary;
	}

	// ------------------------------------------------------------------
	// WooCommerce fee
	// ------------------------------------------------------------------

	public function apply_fee( $cart ): void {
		if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
			return;
		}

		$session_data = $this->get_session_data();
		if ( empty( $session_data ) || empty( $session_data['points'] ) ) {
			return;
		}

		$user_id = (int) ( $session_data['user_id'] ?? 0 );
		$points  = (int) ( $session_data['points'] ?? 0 );

		if ( $user_id <= 0 || $points <= 0 || get_current_user_id() !== $user_id ) {
			$this->clear_session_data();
			return;
		}

		$settings = RewardSettingsAPI::get_reward_settings();
		if ( empty( $settings['enabled'] ) ) {
			$this->clear_session_data();
			return;
		}

		$badge = $this->resolve_user_badge( $user_id, $settings );
		if ( ! $badge ) {
			$this->clear_session_data();
			return;
		}

		$result = $this->calculate_redemption( $points, $user_id, $settings, $badge );
		if ( $result['points'] <= 0 || $result['discount'] <= 0 ) {
			$this->clear_session_data();
			return;
		}

		$discount = (float) $result['discount'];

		if (
			$result['points'] !== $points
			|| (float) ( $session_data['discount'] ?? 0 ) !== $discount
		) {
			$this->set_session_data(
				array(
					'user_id'  => $user_id,
					'points'   => $result['points'],
					'discount' => $discount,
				)
			);
		}

		if ( $discount > 0 ) {
			$cart->add_fee(
				__( 'Reward Points Discount', 'ugcify' ),
				-$discount,
				false
			);
		}
	}

	// ------------------------------------------------------------------
	// AJAX handlers
	// ------------------------------------------------------------------

	public function ajax_apply_points(): void {
		check_ajax_referer( 'ugcify_redeem_nonce', 'nonce' );

		$user_id = get_current_user_id();
		if ( 0 === $user_id ) {
			wp_send_json_error( array( 'message' => __( 'You must be logged in.', 'ugcify' ) ) );
		}

		$raw_requested = trim( sanitize_text_field( wp_unslash( $_POST['points'] ?? '' ) ) );
		if ( '' === $raw_requested ) {
			wp_send_json_error( array( 'message' => __( 'Please enter the number of points to redeem.', 'ugcify' ) ) );
		}

		if ( ! preg_match( '/^[1-9]\d*$/', $raw_requested ) ) {
			wp_send_json_error( array( 'message' => __( 'Please enter a positive whole number of points.', 'ugcify' ) ) );
		}

		$requested = (int) $raw_requested;

		$settings = RewardSettingsAPI::get_reward_settings();
		if ( empty( $settings['enabled'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Rewards are not enabled.', 'ugcify' ) ) );
		}

		$badge = $this->resolve_user_badge( $user_id, $settings );
		if ( ! $badge ) {
			wp_send_json_error( array( 'message' => $this->get_badge_unavailable_message( $settings ) ) );
		}

		$manager    = new PointsManager();
		$balance    = $manager->get_balance( $user_id );
		$max_redeem = (int) ( $settings['max_redeem_threshold'] ?? 500 );

		if ( $requested > $balance ) {
			wp_send_json_error( array( 'message' => __( 'You do not have enough points', 'ugcify' ) ) );
		}

		if ( $requested > $max_redeem ) {
			wp_send_json_error(
				array(
					'message' => sprintf(
						/* translators: %d: maximum points allowed per order */
						__( 'You can redeem a maximum of %d points per order.', 'ugcify' ),
						$max_redeem
					),
				)
			);
		}

		$points_limit = $this->get_max_points_for_cart( $balance, $max_redeem, $this->get_cart_subtotal(), $settings, $badge );
		if ( $points_limit > 0 && $requested > $points_limit ) {
			wp_send_json_error(
				array(
					'message' => sprintf(
						/* translators: %d: maximum points allowed for this order */
						__( 'You can redeem a maximum of %d points for this order.', 'ugcify' ),
						$points_limit
					),
				)
			);
		}

		$result = $this->calculate_redemption( $requested, $user_id, $settings, $badge );

		if ( $result['points'] <= 0 ) {
			wp_send_json_error( array( 'message' => $result['message'] ) );
		}

		$this->set_session_data( array(
			'user_id'  => $user_id,
			'points'   => $result['points'],
			'discount' => $result['discount'],
		) );

		wp_send_json_success( array(
			'message'  => $result['message'],
			'points'   => $result['points'],
			'discount' => $result['discount'],
		) );
	}

	public function ajax_remove_points(): void {
		check_ajax_referer( 'ugcify_redeem_nonce', 'nonce' );

		$this->clear_session_data();

		wp_send_json_success( array(
			'message' => __( 'Reward points discount removed.', 'ugcify' ),
		) );
	}

	public function ajax_refresh_widget(): void {
		check_ajax_referer( 'ugcify_redeem_nonce', 'nonce' );

		wp_send_json_success(
			array(
				'html' => $this->get_widget_markup(),
			)
		);
	}

	// ------------------------------------------------------------------
	// Order processing
	// ------------------------------------------------------------------

	public function deduct_points_on_order( $order_id, $posted, $order ): void {
		$this->persist_order_redemption( $order );
	}

	public function deduct_points_on_store_api_order( $order ): void {
		$this->persist_order_redemption( $order );
	}

	private function persist_order_redemption( $order ): void {
		if ( ! $order || ! is_object( $order ) || ! method_exists( $order, 'get_id' ) ) {
			return;
		}

		if ( (int) $order->get_meta( '_ugcify_points_redeemed', true ) > 0 ) {
			$this->clear_session_data();
			return;
		}

		$session_data = $this->get_session_data();
		if ( empty( $session_data ) || empty( $session_data['points'] ) ) {
			return;
		}

		$order_id       = (int) $order->get_id();
		$order_user_id  = method_exists( $order, 'get_customer_id' ) ? (int) $order->get_customer_id() : 0;
		$session_user_id = (int) ( $session_data['user_id'] ?? 0 );
		$user_id        = $order_user_id > 0 ? $order_user_id : $session_user_id;
		$points         = (int) $session_data['points'];
		$discount       = (float) $session_data['discount'];

		if ( $user_id <= 0 || ( $session_user_id > 0 && $session_user_id !== $user_id ) ) {
			return;
		}

		$manager  = new PointsManager();
		$currency = $this->get_currency_symbol();

		$description = sprintf(
			/* translators: %1$s: currency symbol, %2$s: discount amount, %3$d: order ID */
			__( 'Redeemed for %1$s%2$s discount on order #%3$d', 'ugcify' ),
			$currency,
			number_format( $discount, 2 ),
			$order_id
		);

		$deducted = $manager->redeem( $user_id, $points, $description, array(
			'order_id' => $order_id,
			'discount' => $discount,
		) );

		$order->update_meta_data( '_ugcify_points_redeemed', $deducted );
		$order->update_meta_data( '_ugcify_points_discount', $discount );
		$order->save();

		$this->clear_session_data();
	}

	public function clear_session( $order_id ): void {
		$this->clear_session_data();
	}

	// ------------------------------------------------------------------
	// Session helpers
	// ------------------------------------------------------------------

	private function get_session_data(): ?array {
		$woocommerce = $this->get_woocommerce_instance();
		if ( ! $woocommerce || empty( $woocommerce->session ) ) {
			return null;
		}

		$data = $woocommerce->session->get( self::SESSION_KEY );
		return is_array( $data ) ? $data : null;
	}

	private function set_session_data( array $data ): void {
		$woocommerce = $this->get_woocommerce_instance();
		if ( $woocommerce && ! empty( $woocommerce->session ) ) {
			$woocommerce->session->set( self::SESSION_KEY, $data );
		}
	}

	private function clear_session_data(): void {
		$woocommerce = $this->get_woocommerce_instance();
		if ( $woocommerce && ! empty( $woocommerce->session ) ) {
			$woocommerce->session->set( self::SESSION_KEY, null );
		}
	}

	public function render_checkout_coupon_widget( $checkout = null ): void {
		$this->render_widget();
	}

	public function render_cart_coupon_widget(): void {
		$this->render_widget();
	}

	public function render_cart_widget_fallback(): void {
		if ( $this->coupons_enabled() ) {
			return;
		}

		$this->render_widget();
	}

	private function get_widget_markup(): string {
		ob_start();
		$this->render_widget( false );

		return trim( (string) ob_get_clean() );
	}

	public function print_block_widget_template(): void {
		if ( ! $this->is_cart_or_checkout_page() ) {
			return;
		}

		$widget_markup = $this->get_widget_markup();

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

		$this->print_widget_styles();
		?>
		<template id="ugcify-redeem-widget-template"><?php echo $widget_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></template>
		<?php
	}

	// ------------------------------------------------------------------
	// Frontend widget
	// ------------------------------------------------------------------

	public function render_widget( bool $with_styles = true ): void {
		$user_id = get_current_user_id();
		if ( 0 === $user_id ) {
			return;
		}

		$settings = RewardSettingsAPI::get_reward_settings();
		if ( empty( $settings['enabled'] ) ) {
			return;
		}

		$manager = new PointsManager();
		$raw_balance = $manager->get_balance( $user_id );
		$min_cart_amount = (float) ( $settings['min_redeem_amount'] ?? 50 );
		$max_redeem      = (int) ( $settings['max_redeem_threshold'] ?? 500 );
		$cart_subtotal   = $this->get_cart_subtotal();
		$currency        = $this->get_currency_symbol();
		$description     = sprintf(
			/* translators: %1$s: currency symbol, %2$s: minimum cart amount, %3$d: maximum points allowed */
			esc_html__( 'Redeem your points from the coupon area. Your cart subtotal must be at least %1$s%2$s, and you can use up to %3$d points in a single purchase.', 'ugcify' ),
			esc_html( $currency ),
			esc_html( number_format_i18n( $min_cart_amount, 2 ) ),
			$max_redeem
		);

		$session_data = $this->get_session_data();
		$pending_points = 0;
		if ( ! empty( $session_data ) && (int) ( $session_data['user_id'] ?? 0 ) === $user_id ) {
			$pending_points = max( 0, (int) ( $session_data['points'] ?? 0 ) );
		}
		$balance = max( 0, $raw_balance - $pending_points );

		$badge = $this->resolve_user_badge( $user_id, $settings );
		if ( ! $badge ) {
			$panel_is_open = true;
			?>
			<div class="ugcify-redeem-widget" id="ugcify-redeem-widget">
				<div class="ugcify-redeem-widget__toggle woocommerce-form-coupon-toggle">
					<div class="woocommerce-info">
						<?php esc_html_e( 'Want to redeem reward points?', 'ugcify' ); ?>
						<a href="#" role="button" class="showugcpoints" aria-label="<?php esc_attr_e( 'Check reward point eligibility', 'ugcify' ); ?>" aria-controls="ugcify-redeem-widget-panel" aria-expanded="<?php echo $panel_is_open ? 'true' : 'false'; ?>">
							<?php esc_html_e( 'Click here to check eligibility', 'ugcify' ); ?>
						</a>
					</div>
				</div>

				<div class="ugcify-redeem-widget__panel" id="ugcify-redeem-widget-panel"<?php echo $panel_is_open ? '' : ' style="display:none;"'; ?>>
					<p class="ugcify-redeem-widget__desc"><?php echo esc_html( $description ); ?></p>

					<div class="ugcify-redeem-widget__info">
						<span class="ugcify-redeem-widget__balance">
							<?php
							printf(
								/* translators: %s: points balance value */
								esc_html__( 'Your balance: %s points', 'ugcify' ),
								'<strong>' . esc_html( number_format_i18n( $balance ) ) . '</strong>'
							);
							?>
						</span>
					</div>

					<p class="ugcify-redeem-widget__note">
						<?php echo esc_html( $this->get_badge_unavailable_message( $settings ) ); ?>
					</p>
				</div>
			</div>
			<?php
			if ( $with_styles ) {
				$this->print_widget_styles();
			}
			return;
		}

		$redeemable = $this->get_max_points_for_cart( $raw_balance, $max_redeem, $cart_subtotal, $settings, $badge );
		$is_applied   = ! empty( $session_data['points'] );

		$conversion_points = $this->get_conversion_points( $badge );
		$conv_amount       = (float) ( $badge['amount'] ?? 0 );
		$discount_type     = $this->get_discount_type( $badge );
		$default_points    = $redeemable;
		$can_redeem_points = $redeemable > 0;
		$meets_cart_min    = $cart_subtotal >= $min_cart_amount;
		$panel_is_open     = $is_applied || ! $meets_cart_min || ! $can_redeem_points;
		$toggle_intro      = $is_applied
			? __( 'Reward points applied.', 'ugcify' )
			: __( 'Have reward points?', 'ugcify' );
		$toggle_action     = $is_applied
			? __( 'Click here to manage them', 'ugcify' )
			: __( 'Click here to redeem them', 'ugcify' );
		$toggle_aria_label = $is_applied
			? __( 'Manage applied reward points', 'ugcify' )
			: __( 'Redeem reward points', 'ugcify' );

		$nonce = wp_create_nonce( 'ugcify_redeem_nonce' );
		?>
		<div class="ugcify-redeem-widget" id="ugcify-redeem-widget">
			<div class="ugcify-redeem-widget__toggle woocommerce-form-coupon-toggle">
				<div class="woocommerce-info">
					<?php echo esc_html( $toggle_intro ); ?>
					<a href="#" role="button" class="showugcpoints" aria-label="<?php echo esc_attr( $toggle_aria_label ); ?>" aria-controls="ugcify-redeem-widget-panel" aria-expanded="<?php echo $panel_is_open ? 'true' : 'false'; ?>">
						<?php echo esc_html( $toggle_action ); ?>
					</a>
				</div>
			</div>

			<div class="ugcify-redeem-widget__panel" id="ugcify-redeem-widget-panel"<?php echo $panel_is_open ? '' : ' style="display:none;"'; ?>>
				<p class="ugcify-redeem-widget__desc"><?php echo esc_html( $description ); ?></p>

				<div class="ugcify-redeem-widget__info">
					<span class="ugcify-redeem-widget__balance">
						<?php
						printf(
							/* translators: %s: user's current points balance */
							esc_html__( 'Your balance: %s points', 'ugcify' ),
							'<strong>' . esc_html( number_format_i18n( $balance ) ) . '</strong>'
						);
						?>
					</span>
					<?php if ( $conv_amount > 0 ) : ?>
						<span class="ugcify-redeem-widget__rate">
							<?php
							if ( 'discount' === $discount_type ) {
								printf(
									/* translators: %1$d: conversion points amount, %2$s: percentage discount */
									esc_html__( 'Rate: %1$d pts = %2$s%%', 'ugcify' ),
									(int) $conversion_points,
									esc_html( number_format_i18n( $conv_amount, 2 ) )
								);
							} else {
								printf(
									/* translators: %1$d: conversion points amount, %2$s: currency symbol, %3$s: conversion amount */
									esc_html__( 'Rate: %1$d pts = %2$s%3$s', 'ugcify' ),
									(int) $conversion_points,
									esc_html( $currency ),
									esc_html( number_format_i18n( $conv_amount, 2 ) )
								);
							}
							?>
						</span>
					<?php endif; ?>
				</div>

				<?php if ( ! $meets_cart_min ) : ?>
					<p class="ugcify-redeem-widget__note">
						<?php
					printf(
						/* translators: %1$s: currency symbol, %2$s: minimum cart amount, %3$s: currency symbol, %4$s: current cart subtotal */
							esc_html__( 'Redeem points becomes available once your cart subtotal reaches %1$s%2$s. Current subtotal: %3$s%4$s.', 'ugcify' ),
							esc_html( $currency ),
							esc_html( number_format_i18n( $min_cart_amount, 2 ) ),
							esc_html( $currency ),
							esc_html( number_format_i18n( $cart_subtotal, 2 ) )
						);
						?>
					</p>
				<?php elseif ( ! $can_redeem_points ) : ?>
					<p class="ugcify-redeem-widget__note">
						<?php
						esc_html_e( 'No redeemable discount is available for the current cart.', 'ugcify' );
						?>
					</p>
				<?php elseif ( $is_applied ) : ?>
					<div class="ugcify-redeem-widget__applied">
						<p class="ugcify-redeem-widget__applied-msg">
							<?php
							printf(
								/* translators: %1$d: points applied, %2$s: currency symbol, %3$s: discount amount */
								esc_html__( '%1$d points applied - %2$s%3$s discount', 'ugcify' ),
								(int) $session_data['points'],
								esc_html( $currency ),
								esc_html( number_format_i18n( (float) $session_data['discount'], 2 ) )
							);
							?>
						</p>
						<button type="button" class="button ugcify-redeem-widget__remove" id="ugcify-remove-points"
							data-nonce="<?php echo esc_attr( $nonce ); ?>">
							<?php esc_html_e( 'Remove', 'ugcify' ); ?>
						</button>
					</div>
				<?php else : ?>
					<div class="ugcify-redeem-widget__form">
						<p class="form-row form-row-first ugcify-redeem-widget__field">
							<label for="ugcify-points-input" class="ugcify-redeem-widget__label">
								<?php
								printf(
									/* translators: %d: maximum redeemable points for this order */
									esc_html__( 'Points to redeem (up to %d points this order):', 'ugcify' ),
								(int) $redeemable
								);
								?>
							</label>
							<input type="number" id="ugcify-points-input" class="input-text ugcify-redeem-widget__input"
								min="1"
								max="<?php echo esc_attr( $redeemable ); ?>"
								step="1"
								value="<?php echo esc_attr( $default_points ); ?>" />
						</p>
						<p class="form-row form-row-last ugcify-redeem-widget__actions-row">
							<button type="button" class="button ugcify-redeem-widget__apply" id="ugcify-apply-points"
								data-nonce="<?php echo esc_attr( $nonce ); ?>">
								<?php esc_html_e( 'Apply reward', 'ugcify' ); ?>
							</button>
						</p>
						<div class="clear"></div>
					</div>
				<?php endif; ?>

				<div class="ugcify-redeem-widget__feedback" id="ugcify-redeem-feedback" style="display:none;"></div>
			</div>
		</div>
		<?php
		if ( $with_styles ) {
			$this->print_widget_styles();
		}
	}

	/**
	 * Enqueue widget CSS and inline JS via WordPress APIs.
	 * Replaces the former print_widget_styles() / print_inline_script() echoes.
	 */
	public function enqueue_widget_assets(): void {
		if ( ! $this->is_cart_or_checkout_page() ) {
			return;
		}

		// --- Styles ---
		$css = '
			.ugcify-redeem-widget{margin:12px 0 0}
			.woocommerce-cart-form .coupon .ugcify-redeem-widget{clear:both;display:block;width:100%;padding-top:10px}
			.ugcify-redeem-widget__toggle .woocommerce-info{margin:0 0 12px !important}
			.ugcify-redeem-widget__toggle .woocommerce-info .showugcpoints{font-weight:600}
			.ugcify-redeem-widget__panel{padding:16px 18px;border:1px solid rgba(0,0,0,.1);background:#fafafa}
			.ugcify-redeem-widget__desc{margin:0 0 12px;font-size:13px;line-height:1.5;color:#555}
			.ugcify-redeem-widget__info{display:flex;justify-content:space-between;flex-wrap:wrap;gap:6px;margin-bottom:12px;font-size:13px;color:#555}
			.ugcify-redeem-widget__rate{color:#0073aa;font-weight:500}
			.ugcify-redeem-widget__note{margin:0;font-size:13px;color:#999}
			.ugcify-redeem-widget__label{display:block;margin-bottom:6px;font-size:13px;color:#444}
			.ugcify-redeem-widget__form{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end}
			.ugcify-redeem-widget__field,.ugcify-redeem-widget__actions-row{margin:0}
			.ugcify-redeem-widget__field{flex:1 1 220px}
			.ugcify-redeem-widget__actions-row{flex:0 0 auto}
			.ugcify-redeem-widget__input{width:100%;max-width:220px;padding:6px 10px;border:1px solid #ccc;border-radius:4px;font-size:14px}
			.ugcify-redeem-widget__apply,.ugcify-redeem-widget__remove{padding:6px 16px;font-size:13px;cursor:pointer}
			.ugcify-redeem-widget__applied{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
			.ugcify-redeem-widget__applied-msg{margin:0;font-size:14px;font-weight:500;color:#00a32a}
			.ugcify-redeem-widget__remove{color:#d63638;border-color:#d63638}
			#ugcify-redeem-feedback{margin-top:8px;font-size:13px;font-weight:500}
			@media (max-width:782px){.ugcify-redeem-widget__form{flex-direction:column;align-items:stretch}.ugcify-redeem-widget__actions-row,.ugcify-redeem-widget__field{width:100%}.ugcify-redeem-widget__input{max-width:none}}
		';
		wp_register_style( 'ugcify-checkout-redemption', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters
		wp_enqueue_style( 'ugcify-checkout-redemption' );
		wp_add_inline_style( 'ugcify-checkout-redemption', $css );

		// --- Script ---
		$ajax_url      = esc_js( admin_url( 'admin-ajax.php' ) );
		$refresh_nonce = esc_js( wp_create_nonce( 'ugcify_redeem_nonce' ) );
		$err_enter     = esc_js( __( 'Please enter the number of points to redeem.', 'ugcify' ) );
		$err_positive  = esc_js( __( 'Please enter a positive whole number of points.', 'ugcify' ) );
		$err_generic   = esc_js( __( 'Something went wrong. Please try again.', 'ugcify' ) );

		wp_register_script( 'ugcify-checkout-redemption', false, array( 'jquery' ), UGCIFY_VERSION, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters
		wp_enqueue_script( 'ugcify-checkout-redemption' );
		wp_localize_script(
			'ugcify-checkout-redemption',
			'ugcRedeemParams',
			array(
				'ajaxUrl'      => admin_url( 'admin-ajax.php' ),
				'refreshNonce' => wp_create_nonce( 'ugcify_redeem_nonce' ),
				'i18n'         => array(
					'enterPoints'   => __( 'Please enter the number of points to redeem.', 'ugcify' ),
					'positiveWhole' => __( 'Please enter a positive whole number of points.', 'ugcify' ),
					'genericError'  => __( 'Something went wrong. Please try again.', 'ugcify' ),
				),
			)
		);

		$script = $this->build_inline_script( $ajax_url, $refresh_nonce, $err_enter, $err_positive, $err_generic );
		wp_add_inline_script( 'ugcify-checkout-redemption', $script );
	}

	/**
	 * Build the checkout redemption inline script string.
	 */
	private function build_inline_script( string $ajax_url, string $refresh_nonce, string $err_enter, string $err_positive, string $err_generic ): string {
		return "(function(){
			var ajaxUrl = '{$ajax_url}';
			var refreshNonce = '{$refresh_nonce}';
			var refreshTimer = null;
			var refreshInFlight = false;
			var widgetRefreshEnabled = false;
			var lastCartSignature = '';
			var lastPanelExpanded = null;
			function getWidgetTemplate(){return document.getElementById('ugcify-redeem-widget-template');}
			function getWidgetToggle(){return document.querySelector('#ugcify-redeem-widget .showugcpoints');}
			function getWidgetPanel(){return document.getElementById('ugcify-redeem-widget-panel');}
			function hasRedeemSurface(){var template=getWidgetTemplate();return !!document.getElementById('ugcify-redeem-widget')||!!(template&&template.content&&template.content.firstElementChild);}
			function getBlockWidgetTarget(){return document.querySelector('.wp-block-woocommerce-cart-order-summary-coupon-form-block, .wp-block-woocommerce-checkout-order-summary-coupon-form-block');}
			function getCartWidgetTarget(){return document.querySelector('.woocommerce-cart-form .coupon');}
			function getCheckoutWidgetTarget(){return document.querySelector('.woocommerce-form-coupon-toggle');}
			function setWidgetTemplateHtml(html){var template=getWidgetTemplate();if(template){template.innerHTML=html||'';}}
			function getWidgetExpandedState(){var toggle=getWidgetToggle();var panel=getWidgetPanel();if(!toggle||!panel){return null;}if(toggle.getAttribute('aria-expanded')==='true'){return true;}if(toggle.getAttribute('aria-expanded')==='false'){return false;}if(panel.style.display){return panel.style.display!=='none';}if(typeof window.getComputedStyle==='function'){return window.getComputedStyle(panel).display!=='none';}return true;}
			function applyWidgetExpandedState(expanded){if(expanded===null){return;}var toggle=getWidgetToggle();var panel=getWidgetPanel();if(!toggle||!panel){return;}toggle.setAttribute('aria-expanded',expanded?'true':'false');panel.style.display=expanded?'block':'none';}
			function mountWidgetHtml(html){var existing=document.getElementById('ugcify-redeem-widget');var blockTarget=getBlockWidgetTarget();var cartTarget=getCartWidgetTarget();var checkoutTarget=getCheckoutWidgetTarget();var previousExpanded=existing?getWidgetExpandedState():lastPanelExpanded;setWidgetTemplateHtml(html);if(!html){if(existing){existing.remove();}widgetRefreshEnabled=false;lastPanelExpanded=previousExpanded;return false;}widgetRefreshEnabled=true;if(existing){existing.insertAdjacentHTML('afterend',html);existing.remove();applyWidgetExpandedState(previousExpanded);lastPanelExpanded=previousExpanded;return true;}if(blockTarget){blockTarget.insertAdjacentHTML('afterend',html);applyWidgetExpandedState(previousExpanded);lastPanelExpanded=previousExpanded;return true;}if(cartTarget){cartTarget.insertAdjacentHTML('beforeend',html);applyWidgetExpandedState(previousExpanded);lastPanelExpanded=previousExpanded;return true;}if(checkoutTarget){checkoutTarget.insertAdjacentHTML('afterend',html);applyWidgetExpandedState(previousExpanded);lastPanelExpanded=previousExpanded;return true;}return false;}
			function getCartSignature(){var selectors=['.wp-block-woocommerce-cart-order-summary-block','.wp-block-woocommerce-checkout-order-summary-block','.wc-block-components-sidebar-layout','.cart_totals','.woocommerce-cart-form'];return selectors.map(function(selector){var node=document.querySelector(selector);if(!node){return '';}var clone=node.cloneNode(true);var widget=clone.querySelector('#ugcify-redeem-widget');if(widget){widget.remove();}return clone.textContent?clone.textContent.replace(/\s+/g,' ').trim():'';}).join('|');}
			function refreshWidget(force){var widgetMissing=!document.getElementById('ugcify-redeem-widget');var signature=getCartSignature();if(!widgetRefreshEnabled){return;}if(!force&&!widgetMissing&&signature&&signature===lastCartSignature){return;}if(refreshInFlight){return;}refreshInFlight=true;var fd=new window.FormData();fd.append('action','ugcify_refresh_points_widget');fd.append('nonce',refreshNonce);fetch(ajaxUrl,{method:'POST',body:fd,credentials:'same-origin'}).then(function(r){return r.json();}).then(function(res){if(!res||!res.success||!res.data){return;}mountWidgetHtml(typeof res.data.html==='string'?res.data.html:'');lastCartSignature=getCartSignature()||signature;}).catch(function(){}).then(function(){refreshInFlight=false;});}
			function scheduleWidgetRefresh(force){if(!widgetRefreshEnabled){return;}window.clearTimeout(refreshTimer);refreshTimer=window.setTimeout(function(){refreshWidget(!!force);},180);}
			function cloneBlockWidget(){if(document.getElementById('ugcify-redeem-widget')){return true;}var template=getWidgetTemplate();var target=getBlockWidgetTarget();if(!template||!template.content||!template.content.firstElementChild||!target||!target.parentNode){return false;}var widget=template.content.firstElementChild.cloneNode(true);target.insertAdjacentElement('afterend',widget);return true;}
			function ensureBlockWidget(){var hasBlocks=document.querySelector('.wp-block-woocommerce-cart, .wp-block-woocommerce-checkout');if(!hasBlocks){return;}cloneBlockWidget();lastCartSignature=getCartSignature();if(typeof window.MutationObserver==='undefined'){return;}var observer=new window.MutationObserver(function(){cloneBlockWidget();scheduleWidgetRefresh(false);});observer.observe(document.body,{childList:true,subtree:true});}
			function refreshTotals(){var hasBlocks=document.querySelector('.wp-block-woocommerce-cart, .wp-block-woocommerce-checkout');if(hasBlocks){window.location.reload();return;}if(typeof jQuery!=='undefined'){jQuery(document.body).trigger('update_checkout');jQuery(document.body).trigger('wc_update_cart');}else{location.reload();}}
			function feedback(msg,isError){var el=document.getElementById('ugcify-redeem-feedback');if(!el)return;el.textContent=msg;el.style.display='block';el.style.color=isError?'#d63638':'#00a32a';}
			widgetRefreshEnabled=hasRedeemSurface();
			lastCartSignature=getCartSignature();
			lastPanelExpanded=getWidgetExpandedState();
			if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',ensureBlockWidget);}else{ensureBlockWidget();}
			if(typeof jQuery!=='undefined'){jQuery(function(){jQuery(document.body).on('updated_wc_div updated_cart_totals updated_checkout applied_coupon removed_coupon',function(){scheduleWidgetRefresh(true);});});}
			document.addEventListener('click',function(e){
				var toggle=e.target&&e.target.closest?e.target.closest('.showugcpoints'):null;
				if(toggle){e.preventDefault();var panel=document.getElementById(toggle.getAttribute('aria-controls'));if(!panel)return;var expanded=toggle.getAttribute('aria-expanded')==='true';lastPanelExpanded=!expanded;toggle.setAttribute('aria-expanded',lastPanelExpanded?'true':'false');panel.style.display=lastPanelExpanded?'block':'none';return;}
				if(e.target&&e.target.id==='ugcify-apply-points'){var pts=document.getElementById('ugcify-points-input');var nonce=e.target.getAttribute('data-nonce');var rawPoints=pts&&pts.value?String(pts.value).trim():'';if(!pts||!rawPoints){feedback('{$err_enter}',true);return;}if(!/^[1-9]\d*\$/.test(rawPoints)){feedback('{$err_positive}',true);return;}e.target.disabled=true;var fd=new window.FormData();fd.append('action','ugcify_apply_points');fd.append('nonce',nonce);fd.append('points',rawPoints);fetch(ajaxUrl,{method:'POST',body:fd,credentials:'same-origin'}).then(function(r){return r.json();}).then(function(res){if(res.success){feedback(res.data.message,false);refreshTotals();}else{feedback(res.data.message,true);e.target.disabled=false;}}).catch(function(){feedback('{$err_generic}',true);e.target.disabled=false;});}
				if(e.target&&e.target.id==='ugcify-remove-points'){var nonce=e.target.getAttribute('data-nonce');e.target.disabled=true;var fd=new window.FormData();fd.append('action','ugcify_remove_points');fd.append('nonce',nonce);fetch(ajaxUrl,{method:'POST',body:fd,credentials:'same-origin'}).then(function(r){return r.json();}).then(function(res){if(res.success){feedback(res.data.message,false);refreshTotals();}else{feedback(res.data.message,true);e.target.disabled=false;}}).catch(function(){feedback('{$err_generic}',true);e.target.disabled=false;});}
			});
		})();";
	}

	/** @deprecated Handled by enqueue_widget_assets() via wp_add_inline_script(). */
	public function print_inline_script(): void {
		// Script is now output via enqueue_widget_assets() → wp_add_inline_script().
		// This method is kept only to avoid fatal errors if called externally.
	}
	/** @deprecated Styles are now enqueued via enqueue_widget_assets(). */
	private function print_widget_styles(): void {}
}
