<?php
/**
 * Storzen Module — Stock Scarcity Messages
 *
 * Shows "Hurry! Only X left in stock" urgency messages on product pages
 * when remaining inventory falls at or below a configurable threshold.
 * Switches to a more urgent visual + copy at a critical-stock threshold.
 *
 * Works with:
 *   • Simple products (renders directly on the product summary)
 *   • Variable products (re-renders when a variation is selected, via
 *     a small inline script triggered by WC's `found_variation` event)
 *   • Optional shop/category loop display (compact version)
 *
 * Compatibility:
 *   • Read-only — never writes to stock or product meta
 *   • Uses WC's own `managing_stock()`, `is_in_stock()`, `get_stock_quantity()`
 *   • Honors `prefers-reduced-motion` via CSS
 *
 * 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_Stock_Scarcity' ) ) {

	class Storzen_Module_Stock_Scarcity {

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

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

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

			// Render on single product summary (priority 25 = between price and ATC).
			add_action( 'woocommerce_single_product_summary', array( $this, 'render_bar' ), 25 );

			// Storzen-native: render after price on Storzen single product Price widget.
			add_action( 'storzen_product_single_after_price', array( $this, 'render_bar' ), 10, 1 );

			// Variable-product variation watcher script in footer.
			add_action( 'wp_footer', array( $this, 'inject_variation_script' ) );

			// WooCommerce default loop — gated by show_on_shop setting.
			$s = $this->get_settings();
			if ( $s['show_on_shop'] ) {
				add_action( 'woocommerce_after_shop_loop_item_title', array( $this, 'render_bar_loop' ), 12 );
			}
			// Storzen-native hook always registered — widget toggle controls visibility.
			add_action( 'storzen_shop_card_after_title', array( $this, 'render_bar_loop' ), 10, 1 );
		}

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

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

			$icon_choices = array( 'fire', 'clock', 'alert', 'none' );

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

			$message = (string) storzen_module_setting( 'stock_scarcity', 'message' );
			if ( '' === $message ) {
				$message = __( 'Hurry! Only {stock} left in stock', 'storzen' );
			}

			$critical_message = (string) storzen_module_setting( 'stock_scarcity', 'critical_message' );
			if ( '' === $critical_message ) {
				$critical_message = __( 'Almost gone! Only {stock} left', 'storzen' );
			}

			$this->settings = array(
				'threshold'          => max( 1, (int) storzen_module_setting( 'stock_scarcity', 'threshold' ) ),
				'message'            => $message,
				'critical_threshold' => max( 1, (int) storzen_module_setting( 'stock_scarcity', 'critical_threshold' ) ),
				'critical_message'   => $critical_message,
				'show_progress'      => (bool) storzen_module_setting( 'stock_scarcity', 'show_progress' ),
				'show_on_shop'       => (bool) storzen_module_setting( 'stock_scarcity', 'show_on_shop' ),
				'show_on_variable'   => (bool) storzen_module_setting( 'stock_scarcity', 'show_on_variable' ),
				'icon'               => $icon,
			);

			return $this->settings;
		}

		// ─────────────────────────────────────────────────────────────────
		// Public render entry points
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Render on single product summary.
		 *
		 * For variable products we emit an empty placeholder container,
		 * which the variation script populates when a variation is picked.
		 */
		public function render_bar( $product_arg = null ) {
			$product = ( $product_arg instanceof WC_Product ) ? $product_arg : ( $GLOBALS['product'] ?? null );
			if ( ! $product instanceof WC_Product ) {
				return;
			}

			if ( $product->is_type( 'variable' ) ) {
				$s = $this->get_settings();
				if ( ! $s['show_on_variable'] ) {
					return;
				}
				// Empty placeholder — filled in by inject_variation_script() on selection.
				printf(
					'<div class="sz-ss-wrap" data-product-id="%s" hidden></div>',
					esc_attr( (string) $product->get_id() )
				);
				return;
			}

			// Simple / grouped / external products.
			echo $this->get_bar_html_for_product( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped inside.
		}

		/**
		 * Render bar inside the shop/category loop (compact variant).
		 */
		public function render_bar_loop( $product_arg = null ) {
			if ( class_exists( 'Elementor\Storzen_Archive_Grid' ) ) {
				$ctx = \Elementor\Storzen_Archive_Grid::is_module_active( 'stock_scarcity' );
				if ( false === $ctx ) {
					return;
				}
			}
			$product = ( $product_arg instanceof WC_Product ) ? $product_arg : ( $GLOBALS['product'] ?? null );
			if ( ! $product instanceof WC_Product || $product->is_type( 'variable' ) ) {
				return;
			}
			echo $this->get_bar_html_for_product( $product, true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped inside.
		}

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

		/**
		 * Build the bar HTML for a product, or empty string if it doesn't qualify.
		 *
		 * @param WC_Product $product Product object.
		 * @param bool       $compact True for the loop / card variant.
		 * @return string
		 */
		public function get_bar_html_for_product( $product, $compact = false ) {
			if ( ! $product instanceof WC_Product ) {
				return '';
			}
			// Must manage stock and be in-stock to show anything.
			if ( ! $product->managing_stock() || ! $product->is_in_stock() ) {
				return '';
			}

			$stock = $product->get_stock_quantity();
			if ( null === $stock || $stock <= 0 ) {
				return '';
			}

			$s         = $this->get_settings();
			$threshold = (int) $s['threshold'];

			// Only render if stock is at or below the warning threshold.
			if ( (int) $stock > $threshold ) {
				return '';
			}

			return $this->build_bar_markup( (int) $stock, $compact );
		}

		/**
		 * Build the actual bar markup for a given remaining-stock value.
		 *
		 * @param int  $stock   Remaining stock quantity.
		 * @param bool $compact True for the loop / card variant.
		 * @return string
		 */
		private function build_bar_markup( $stock, $compact = false ) {
			$s = $this->get_settings();

			$is_critical = ( $stock <= (int) $s['critical_threshold'] );

			$icon_map = array(
				'fire'  => '🔥',
				'clock' => '⏰',
				'alert' => '⚠️',
				'none'  => '',
			);
			$icon = isset( $icon_map[ $s['icon'] ] ) ? $icon_map[ $s['icon'] ] : '🔥';

			// Build the message. We escape the raw template, then inject the
			// {stock} value wrapped in <strong>, then run wp_kses to whitelist
			// only that single tag. This keeps translator-supplied messages safe.
			$msg_raw = $is_critical ? $s['critical_message'] : $s['message'];
			$message = str_replace(
				'{stock}',
				'<strong>' . esc_html( (string) $stock ) . '</strong>',
				esc_html( $msg_raw )
			);
			$message = wp_kses( $message, array( 'strong' => array() ) );

			// Progress width — scale from threshold downward, floor at 5%
			// so the bar never looks completely empty.
			$threshold  = (int) $s['threshold'];
			$percentage = ( $threshold > 0 ) ? round( ( $stock / $threshold ) * 100, 2 ) : 0;
			$percentage = min( 100, max( 5, $percentage ) );

			$classes = array( 'sz-ss' );
			if ( $is_critical ) { $classes[] = 'is-critical'; }
			if ( $compact )     { $classes[] = 'sz-ss--compact'; }

			ob_start();
			?>
			<div
				class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>"
				role="status"
				aria-live="polite"
				data-stock="<?php echo esc_attr( (string) $stock ); ?>"
			>
				<div class="sz-ss__message">
					<?php if ( '' !== $icon ) : ?>
						<span class="sz-ss__icon" aria-hidden="true"><?php echo esc_html( $icon ); ?></span>
					<?php endif; ?>
					<span class="sz-ss__text"><?php echo $message; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-escaped + wp_kses'd above. ?></span>
				</div>

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

		// ─────────────────────────────────────────────────────────────────
		// Variable product support
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Inject a small script on variable product pages that watches
		 * WC's `found_variation` event and swaps the bar markup in.
		 *
		 * We build a server-side map of variation_id → pre-rendered HTML,
		 * then localize it to the dedicated JS file. Zero AJAX overhead.
		 */
		public function inject_variation_script() {
			if ( ! function_exists( 'is_product' ) || ! is_product() ) {
				return;
			}

			global $product; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WC global.
			if ( ! $product instanceof WC_Product || ! $product->is_type( 'variable' ) ) {
				return;
			}

			$s = $this->get_settings();
			if ( ! $s['show_on_variable'] ) {
				return;
			}

			$threshold       = (int) $s['threshold'];
			$variation_stock = array();

			foreach ( $product->get_children() as $child_id ) {
				$variation = wc_get_product( $child_id );
				if ( ! $variation instanceof WC_Product ) {
					continue;
				}
				if ( ! $variation->managing_stock() || ! $variation->is_in_stock() ) {
					continue;
				}
				$qty = $variation->get_stock_quantity();
				if ( null === $qty || $qty <= 0 || (int) $qty > $threshold ) {
					continue;
				}
				$variation_stock[ (string) $child_id ] = array(
					'stock' => (int) $qty,
					'html'  => $this->build_bar_markup( (int) $qty ),
				);
			}

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

			// Pass the map to the already-enqueued JS via wp_localize_script.
			// (enqueue_assets() ensures the handle is registered.)
			wp_localize_script(
				'storzen-stock-scarcity',
				'szStockScarcity',
				array(
					'variations' => $variation_stock,
				)
			);
		}

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

		public function enqueue_assets() {
			if ( is_admin() ) {
				return;
			}

			$s = $this->get_settings();

			$on_product    = function_exists( 'is_product' ) && is_product();
			$on_shop       = ( function_exists( 'is_shop' )            && is_shop() )
				|| ( function_exists( 'is_product_category' ) && is_product_category() )
				|| ( function_exists( 'is_product_tag' )      && is_product_tag() );
			$on_elementor  = class_exists( '\Elementor\Plugin' );

			$should_load = $on_product || ( $on_shop && $s['show_on_shop'] ) || $on_elementor;
			if ( ! $should_load ) {
				return;
			}

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

			// JS only needed on single variable-product pages (to swap on variation).
			// Register/enqueue here so inject_variation_script() can localize to it.
			if ( $on_product && $s['show_on_variable'] ) {
				wp_enqueue_script(
					'storzen-stock-scarcity',
					STORZEN_ROOT_URL . 'assets/js/modules/stock-scarcity.js',
					array( 'jquery' ),
					STORZEN_VERSION,
					true
				);
			}
		}

	} // end class

} // end class_exists
