<?php
/**
 * Storzen Module — Free Shipping Progress Bar
 *
 * Calculates remaining amount needed for free shipping and shows a
 * progress bar in the cart, mini-cart, checkout, and optionally as a
 * full-width sticky banner on every page.
 *
 * Works with:
 *   • Cart page (above totals)
 *   • Mini-cart / cart widget
 *   • Checkout page
 *   • Sticky top/bottom announcement bar
 *
 * Compatibility:
 *   • Auto-refreshes via WC cart fragments (no full reload needed)
 *   • Subtotal hook (`storzen_shipping_bar_subtotal`) lets stores
 *     exclude product categories or modify the comparison amount
 *   • Reduced-motion friendly (CSS handles this)
 *
 * Reads settings from the Storzen_Modules registry (no separate option key).
 *
 * @package storzen
 * @since   1.1.0
 */

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

if ( ! class_exists( 'Storzen_Module_Shipping_Bar' ) ) {

	class Storzen_Module_Shipping_Bar {

		/** @var array|null Per-request resolved settings cache */
		private $settings = null;

		public function __construct() {
			if ( ! function_exists( 'WC' ) ) {
				return;
			}

			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );

			// Hook into cart/mini-cart/checkout based on which locations are enabled.
			$this->register_display_hooks();

			// AJAX cart fragments — bar auto-updates on any cart change.
			add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'cart_fragments' ) );

			// Sticky banner — renders unconditionally in footer if enabled.
			add_action( 'wp_footer', array( $this, 'maybe_render_sticky_bar' ), 20 );
		}

		// ─────────────────────────────────────────────────────────────────
		// Settings helper
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Get all module settings with safe defaults.
		 *
		 * @return array
		 */
		private function get_settings() {
			if ( null !== $this->settings ) {
				return $this->settings;
			}

			$icon_choices    = array( 'truck', 'gift', 'cart', 'none' );
			$sticky_choices  = array( 'top', 'bottom' );

			$icon = (string) storzen_module_setting( 'shipping_bar', 'icon' );
			if ( ! in_array( $icon, $icon_choices, true ) ) {
				$icon = 'truck';
			}

			$sticky_pos = (string) storzen_module_setting( 'shipping_bar', 'sticky_position' );
			if ( ! in_array( $sticky_pos, $sticky_choices, true ) ) {
				$sticky_pos = 'top';
			}

			$initial = (string) storzen_module_setting( 'shipping_bar', 'initial_msg' );
			if ( '' === $initial ) {
				$initial = __( 'Spend {remaining} more to get FREE shipping!', 'storzen' );
			}

			$achieved = (string) storzen_module_setting( 'shipping_bar', 'achieved_msg' );
			if ( '' === $achieved ) {
				$achieved = __( "You've unlocked FREE shipping!", 'storzen' );
			}

			$this->settings = array(
				'threshold'         => max( 0, (float) storzen_module_setting( 'shipping_bar', 'threshold' ) ),
				'initial_msg'       => $initial,
				'achieved_msg'      => $achieved,
				'show_on_cart'      => (bool) storzen_module_setting( 'shipping_bar', 'show_on_cart' ),
				'show_on_mini_cart' => (bool) storzen_module_setting( 'shipping_bar', 'show_on_mini_cart' ),
				'show_on_checkout'  => (bool) storzen_module_setting( 'shipping_bar', 'show_on_checkout' ),
				'show_on_sticky'    => (bool) storzen_module_setting( 'shipping_bar', 'show_on_sticky' ),
				'sticky_position'   => $sticky_pos,
				'icon'              => $icon,
				'animate'           => (bool) storzen_module_setting( 'shipping_bar', 'animate' ),
				'show_progress'     => (bool) storzen_module_setting( 'shipping_bar', 'show_progress' ),
			);

			return $this->settings;
		}

		// ─────────────────────────────────────────────────────────────────
		// Display hook registration
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Attach the bar render to WC's cart/mini-cart/checkout hooks based
		 * on which locations the merchant enabled. Sticky is handled separately
		 * via wp_footer because it lives outside cart pages.
		 */
		private function register_display_hooks() {
			$s = $this->get_settings();

			if ( $s['show_on_cart'] ) {
				// Above the cart totals.
				add_action( 'woocommerce_before_cart_totals', array( $this, 'render_bar_cart' ), 10 );
				// Fallback for themes that omit the totals hook.
				add_action( 'woocommerce_before_cart',        array( $this, 'render_bar_cart' ), 5 );
			}

			if ( $s['show_on_mini_cart'] ) {
				add_action( 'woocommerce_before_mini_cart', array( $this, 'render_bar_mini' ), 10 );
			}

			if ( $s['show_on_checkout'] ) {
				add_action( 'woocommerce_before_checkout_form', array( $this, 'render_bar_checkout' ), 5 );
			}
		}

		// ─────────────────────────────────────────────────────────────────
		// Render entry points (one per context for clean hook signatures)
		// ─────────────────────────────────────────────────────────────────

		public function render_bar_cart() {
			echo $this->get_bar_html( 'cart' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped inside.
		}

		public function render_bar_mini() {
			echo $this->get_bar_html( 'mini_cart' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped inside.
		}

		public function render_bar_checkout() {
			echo $this->get_bar_html( 'checkout' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped inside.
		}

		/**
		 * Render the sticky top/bottom banner if enabled.
		 */
		public function maybe_render_sticky_bar() {
			$s = $this->get_settings();
			if ( ! $s['show_on_sticky'] ) {
				return;
			}
			if ( is_admin() ) {
				return;
			}
			if ( class_exists( 'Elementor\Storzen_Archive_Grid' ) && false === \Elementor\Storzen_Archive_Grid::is_page_module_enabled( 'shipping_bar' ) ) {
				return;
			}
			if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
				return;
			}
			echo $this->get_bar_html( 'sticky' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped inside.
		}

		// ─────────────────────────────────────────────────────────────────
		// Bar HTML builder
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Build the bar markup for a given display context.
		 *
		 * @param string $context cart | mini_cart | checkout | sticky
		 * @return string Rendered HTML.
		 */
		public function get_bar_html( $context = 'cart' ) {
			$state    = $this->calculate_state();
			$settings = $this->get_settings();

			$message_raw = $state['achieved']
				? $settings['achieved_msg']
				: $settings['initial_msg'];

			// Substitute placeholders. Values are formatted through wc_price()
			// which returns wrapped HTML — wp_kses_post lets us keep that markup
			// while stripping anything unexpected.
			$message = str_replace(
				array( '{remaining}', '{threshold}', '{subtotal}' ),
				array(
					'<span class="sz-sb__amount">' . wp_kses_post( wc_price( $state['remaining'] ) ) . '</span>',
					wp_kses_post( wc_price( $settings['threshold'] ) ),
					wp_kses_post( wc_price( $state['subtotal'] ) ),
				),
				$message_raw
			);

			$icon_map = array(
				'truck' => '🚚',
				'gift'  => '🎁',
				'cart'  => '🛒',
				'none'  => '',
			);
			$icon = isset( $icon_map[ $settings['icon'] ] ) ? $icon_map[ $settings['icon'] ] : '';

			$classes = array(
				'sz-sb',
				'sz-sb--' . sanitize_html_class( $context ),
			);
			if ( $state['achieved'] ) {
				$classes[] = 'is-achieved';
			}
			if ( 'sticky' === $context ) {
				$classes[] = 'sz-sb--sticky-' . sanitize_html_class( $settings['sticky_position'] );
			}
			if ( ! $settings['animate'] ) {
				$classes[] = 'sz-sb--no-animate';
			}

			ob_start();
			?>
			<div
				class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>"
				data-percentage="<?php echo esc_attr( (string) $state['percentage'] ); ?>"
				role="status"
				aria-live="polite"
			>
				<div class="sz-sb__message">
					<?php if ( '' !== $icon ) : ?>
						<span class="sz-sb__icon" aria-hidden="true"><?php echo esc_html( $icon ); ?></span>
					<?php endif; ?>
					<span class="sz-sb__text"><?php echo wp_kses_post( $message ); ?></span>
				</div>

				<?php if ( $settings['show_progress'] && 'sticky' !== $context ) : ?>
					<div class="sz-sb__progress">
						<div
							class="sz-sb__progress-fill"
							style="width: <?php echo esc_attr( (string) $state['percentage'] ); ?>%;"
						></div>
					</div>
				<?php endif; ?>
			</div>
			<?php
			return ob_get_clean();
		}

		/**
		 * Calculate the cart state vs. the threshold.
		 *
		 * @return array{subtotal: float, threshold: float, remaining: float, percentage: float, achieved: bool}
		 */
		public function calculate_state() {
			$settings  = $this->get_settings();
			$threshold = (float) $settings['threshold'];

			$subtotal = 0.0;
			if ( function_exists( 'WC' ) && WC()->cart ) {
				$subtotal = (float) WC()->cart->get_subtotal();

				/**
				 * Filter the subtotal used for the free-shipping comparison.
				 *
				 * Useful for excluding certain product categories or for
				 * applying custom business rules.
				 *
				 * @param float    $subtotal The current cart subtotal.
				 * @param WC_Cart  $cart     The WC cart object.
				 */
				$subtotal = (float) apply_filters( 'storzen_shipping_bar_subtotal', $subtotal, WC()->cart );
			}

			$remaining = max( 0.0, $threshold - $subtotal );
			$achieved  = ( $threshold > 0 && $subtotal >= $threshold );
			$percent   = ( $threshold > 0 ) ? min( 100, ( $subtotal / $threshold ) * 100 ) : 0;

			return array(
				'subtotal'   => $subtotal,
				'threshold'  => $threshold,
				'remaining'  => $remaining,
				'percentage' => round( $percent, 2 ),
				'achieved'   => $achieved,
			);
		}

		// ─────────────────────────────────────────────────────────────────
		// AJAX cart fragments — auto-refresh on cart updates
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Register the bar as a WC cart fragment so it refreshes whenever
		 * any add-to-cart / remove / update fires.
		 *
		 * @param array $fragments Existing fragments map.
		 * @return array
		 */
		public function cart_fragments( $fragments ) {
			if ( ! is_array( $fragments ) ) {
				$fragments = array();
			}

			$settings = $this->get_settings();

			if ( $settings['show_on_cart'] ) {
				$fragments['div.sz-sb--cart'] = $this->get_bar_html( 'cart' );
			}

			if ( $settings['show_on_sticky'] ) {
				$fragments[ '.sz-sb--sticky-' . $settings['sticky_position'] ] = $this->get_bar_html( 'sticky' );
			}

			return $fragments;
		}

		// ─────────────────────────────────────────────────────────────────
		// Asset enqueue (only where bar will render)
		// ─────────────────────────────────────────────────────────────────

		public function enqueue_assets() {
			if ( is_admin() ) {
				return;
			}
			if ( class_exists( 'Elementor\Storzen_Archive_Grid' ) && false === \Elementor\Storzen_Archive_Grid::is_page_module_enabled( 'shipping_bar' ) ) {
				return;
			}

			$s = $this->get_settings();

			$should_load = false;
			if ( $s['show_on_sticky'] ) {
				$should_load = true; // Sticky shows on every page.
			} elseif ( $s['show_on_mini_cart'] ) {
				$should_load = true; // Mini-cart can pop up anywhere.
			} elseif ( $s['show_on_cart'] && function_exists( 'is_cart' ) && is_cart() ) {
				$should_load = true;
			} elseif ( $s['show_on_checkout'] && function_exists( 'is_checkout' ) && is_checkout() ) {
				$should_load = true;
			}

			if ( ! $should_load ) {
				return;
			}

			wp_enqueue_style(
				'storzen-shipping-bar',
				STORZEN_ROOT_URL . 'assets/css/modules/shipping-bar.css',
				array(),
				STORZEN_VERSION
			);

			// JS only loaded when needed: sticky offset OR animation.
			if ( $s['show_on_sticky'] || $s['animate'] ) {
				wp_enqueue_script(
					'storzen-shipping-bar',
					STORZEN_ROOT_URL . 'assets/js/modules/shipping-bar.js',
					array( 'jquery' ),
					STORZEN_VERSION,
					true
				);
			}
		}

	} // end class

} // end class_exists
