<?php
/**
 * Storzen Module — Cart Cross-Sell Popup (Pro)
 *
 * Shows a modal of related products after the customer adds something to
 * cart. Goal: nudge upsells before checkout.
 *
 * Behavior:
 *   • Listens for WC's `added_to_cart` JS event
 *   • AJAX-fetches up to N suggestions for the just-added product
 *   • Renders modal with each suggestion's image, title, price, and an
 *     "Add to cart" button that uses WC's native AJAX add-to-cart
 *
 * Source options (configured):
 *   cross_sells   — products marked as cross-sells on the added product
 *   upsells       — same, but upsells
 *   same_category — random products in the same category
 *   best_sellers  — top-selling products in the store
 *   recent        — most recent products
 *
 * Loaded only when STORZEN_IS_PRO === 'yes'.
 *
 * @package storzen
 * @since   1.5.3
 */

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

if ( ! class_exists( 'Storzen_Module_Cart_Cross_Sell' ) ) {

	class Storzen_Module_Cart_Cross_Sell {

		const AJAX_FETCH = 'storzen_cross_sell_fetch';
		const NONCE      = 'storzen_cross_sell_nonce';

		/** @var array|null */
		private $settings = null;

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

			// The "cart" module also owns Ajax/Mini-Cart behaviour (handled in
			// cart-functionality.php). The cross-sell popup itself is opt-in via
			// the `xs_enable` sub-setting — bail out of popup wiring if it's off.
			if ( ! function_exists( 'storzen_module_setting' )
				|| ! storzen_module_setting( 'cart', 'xs_enable' ) ) {
				return;
			}

			add_action( 'wp_enqueue_scripts',                            array( $this, 'enqueue_assets' ) );
			add_action( 'wp_footer',                                     array( $this, 'render_modal_shell' ), 25 );
			add_action( 'wp_ajax_'        . self::AJAX_FETCH,            array( $this, 'ajax_fetch' ) );
			add_action( 'wp_ajax_nopriv_' . self::AJAX_FETCH,            array( $this, 'ajax_fetch' ) );
		}

		// ─────────────────────────────────────────────────────────────────
		// Settings
		// ─────────────────────────────────────────────────────────────────

		private function get_settings() {
			if ( null !== $this->settings ) {
				return $this->settings;
			}

			$valid_triggers = array( 'after_add_to_cart', 'before_checkout', 'cart_page_view' );
			$valid_sources  = array( 'cross_sells', 'upsells', 'same_category', 'best_sellers', 'recent' );
			$valid_layouts  = array( 'grid', 'list', 'carousel' );

			// Settings live on the Modules page under the "cart" module,
			// stored with the `xs_*` keys in `storzen_modules_settings`.
			$get = function ( $key, $default = '' ) {
				if ( ! function_exists( 'storzen_module_setting' ) ) {
					return $default;
				}
				$val = storzen_module_setting( 'cart', $key );
				return ( null === $val || '' === $val ) ? $default : $val;
			};

			$trigger = (string) $get( 'xs_trigger', 'after_add_to_cart' );
			if ( ! in_array( $trigger, $valid_triggers, true ) ) {
				$trigger = 'after_add_to_cart';
			}

			$source = (string) $get( 'xs_source', 'upsells' );
			if ( ! in_array( $source, $valid_sources, true ) ) {
				$source = 'upsells';
			}

			$layout = (string) $get( 'xs_layout', 'grid' );
			if ( ! in_array( $layout, $valid_layouts, true ) ) {
				$layout = 'grid';
			}

			$heading = (string) $get( 'xs_heading' );
			$button  = (string) $get( 'xs_button_text' );
			$skip    = (string) $get( 'xs_skip_text' );

			$this->settings = array(
				'trigger'             => $trigger,
				'source'              => $source,
				'max_products'        => max( 1, min( 8, (int) $get( 'xs_max_products', 3 ) ?: 3 ) ),
				'heading'             => $heading !== '' ? $heading : __( 'You might also like…', 'storzen' ),
				'button_text'         => $button  !== '' ? $button  : __( 'Add to cart', 'storzen' ),
				'skip_text'           => $skip    !== '' ? $skip    : __( 'No thanks, continue', 'storzen' ),
				'layout'              => $layout,
				'show_price'          => (bool) $get( 'xs_show_price', true ),
				'show_rating'         => (bool) $get( 'xs_show_rating', false ),
				'show_discount_badge' => (bool) $get( 'xs_show_discount_badge', true ),
			);
			return $this->settings;
		}

		// ─────────────────────────────────────────────────────────────────
		// Asset enqueue
		// ─────────────────────────────────────────────────────────────────

		public function enqueue_assets() {
			if ( is_admin() ) {
				return;
			}
			$s = $this->get_settings();

			wp_enqueue_style(
				'storzen-cross-sell',
				STORZEN_ROOT_URL . 'assets/css/modules/cart-cross-sell.css',
				array(),
				STORZEN_VERSION
			);

			wp_enqueue_script(
				'storzen-cross-sell',
				STORZEN_ROOT_URL . 'assets/js/modules/cart-cross-sell.js',
				array( 'jquery' ),
				STORZEN_VERSION,
				true
			);

			wp_localize_script(
				'storzen-cross-sell',
				'szCrossSell',
				array(
					'ajaxUrl' => admin_url( 'admin-ajax.php' ),
					'nonce'   => wp_create_nonce( self::NONCE ),
					'trigger' => $s['trigger'],
					'i18n'    => array(
						'loading' => __( 'Loading suggestions…', 'storzen' ),
						'noItems' => __( 'No suggestions available.', 'storzen' ),
						'added'   => __( 'Added!', 'storzen' ),
						'adding'  => __( 'Adding…', 'storzen' ),
						'error'   => __( 'Could not add to cart.', 'storzen' ),
					),
				)
			);
		}

		// ─────────────────────────────────────────────────────────────────
		// Modal shell — empty container in footer; JS fills it
		// ─────────────────────────────────────────────────────────────────

		public function render_modal_shell() {
			if ( is_admin() ) {
				return;
			}
			$s = $this->get_settings();
			?>
			<div class="sz-xs-modal" id="sz-xs-modal" role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="sz-xs-title">
				<div class="sz-xs-modal__overlay" data-sz-xs-close></div>
				<div class="sz-xs-modal__panel">
					<header class="sz-xs-modal__header">
						<h2 class="sz-xs-modal__title" id="sz-xs-title"><?php echo esc_html( $s['heading'] ); ?></h2>
						<button type="button" class="sz-xs-modal__close" data-sz-xs-close aria-label="<?php esc_attr_e( 'Close', 'storzen' ); ?>">&times;</button>
					</header>
					<div class="sz-xs-modal__body" data-sz-xs-body>
						<div class="sz-xs-loading"><?php esc_html_e( 'Loading suggestions…', 'storzen' ); ?></div>
					</div>
					<footer class="sz-xs-modal__footer">
						<button type="button" class="sz-xs-modal__skip" data-sz-xs-close><?php echo esc_html( $s['skip_text'] ); ?></button>
					</footer>
				</div>
			</div>
			<?php
		}

		// ─────────────────────────────────────────────────────────────────
		// AJAX: build suggestion list for a given just-added product
		// ─────────────────────────────────────────────────────────────────

		public function ajax_fetch() {
			if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), self::NONCE ) ) {
				wp_send_json_error( array( 'message' => __( 'Security check failed.', 'storzen' ) ), 403 );
			}

			$product_id = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
			$ids        = $this->collect_suggestions_with_fallback( $product_id );

			if ( empty( $ids ) ) {
				wp_send_json_success(
					array(
						'html'  => '<p class="sz-xs-empty">' . esc_html__( 'No suggestions available right now.', 'storzen' ) . '</p>',
						'count' => 0,
					)
				);
			}

			$html = $this->render_suggestions( $ids );
			wp_send_json_success( array( 'html' => $html, 'count' => count( $ids ) ) );
		}

		/**
		 * Collect suggestions, walking a fallback chain so the popup is never
		 * empty as long as the store has at least a couple of other products.
		 *
		 * Order: configured source → same_category → best_sellers → recent.
		 * Each step is tried only if the previous one returned nothing.
		 *
		 * @param int $product_id
		 * @return int[]
		 */
		private function collect_suggestions_with_fallback( $product_id ) {
			$s     = $this->get_settings();
			$chain = array( $s['source'], 'same_category', 'best_sellers', 'recent' );

			$seen = array();
			foreach ( $chain as $source ) {
				if ( isset( $seen[ $source ] ) ) {
					continue;
				}
				$seen[ $source ] = true;

				$ids = $this->collect_suggestions( $product_id, $source );
				if ( ! empty( $ids ) ) {
					return $ids;
				}
			}

			return array();
		}

		/**
		 * Collect up to max_products product IDs based on the source.
		 * Excludes the just-added product and anything already in cart.
		 *
		 * @param int    $product_id
		 * @param string $source
		 * @return int[]
		 */
		private function collect_suggestions( $product_id, $source ) {
			$s     = $this->get_settings();
			$limit = (int) $s['max_products'];
			$ids   = array();

			$exclude = array( $product_id );
			if ( WC()->cart ) {
				foreach ( WC()->cart->get_cart() as $item ) {
					$exclude[] = (int) $item['product_id'];
				}
			}

			switch ( $source ) {
				case 'cross_sells':
					$p = wc_get_product( $product_id );
					$ids = $p ? $p->get_cross_sell_ids() : array();
					break;
				case 'upsells':
					$p = wc_get_product( $product_id );
					$ids = $p ? $p->get_upsell_ids() : array();
					break;
				case 'same_category':
					$p = wc_get_product( $product_id );
					if ( $p ) {
						$cats = wp_get_post_terms( $product_id, 'product_cat', array( 'fields' => 'ids' ) );
						if ( ! is_wp_error( $cats ) && ! empty( $cats ) ) {
							// phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in,WordPress.DB.SlowDBQuery.slow_db_query_tax_query
							// Intentional: post__not_in excludes only the small list of current cart
							// items; tax_query scopes suggestions to the product's own categories.
							$q = new WP_Query(
								array(
									'post_type'      => 'product',
									'post_status'    => 'publish',
									'posts_per_page' => $limit * 2,
									'fields'         => 'ids',
									'orderby'        => 'rand',
									'post__not_in'   => $exclude,
									'tax_query'      => array(
										array(
											'taxonomy' => 'product_cat',
											'field'    => 'term_id',
											'terms'    => $cats,
										),
									),
								)
							);
							// phpcs:enable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in,WordPress.DB.SlowDBQuery.slow_db_query_tax_query
							$ids = $q->posts;
						}
					}
					break;
				case 'best_sellers':
					// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
					// Intentional: meta_key=total_sales required for best-sellers ordering;
					// post__not_in excludes only the small cart-item list.
					$q = new WP_Query(
						array(
							'post_type'      => 'product',
							'post_status'    => 'publish',
							'posts_per_page' => $limit * 2,
							'fields'         => 'ids',
							'meta_key'       => 'total_sales',
							'orderby'        => 'meta_value_num',
							'order'          => 'DESC',
							'post__not_in'   => $exclude,
						)
					);
					// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
					$ids = $q->posts;
					break;
				case 'recent':
				default:
					// phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
					// Intentional: excludes only the small list of current cart items.
					$q = new WP_Query(
						array(
							'post_type'      => 'product',
							'post_status'    => 'publish',
							'posts_per_page' => $limit * 2,
							'fields'         => 'ids',
							'orderby'        => 'date',
							'order'          => 'DESC',
							'post__not_in'   => $exclude,
						)
					);
					// phpcs:enable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
					$ids = $q->posts;
					break;
			}

			// Filter out non-purchasable and unpublished products, then cap.
			$ids = array_values( array_filter( array_map( 'absint', $ids ), function ( $id ) use ( $exclude ) {
				if ( in_array( $id, $exclude, true ) ) {
					return false;
				}
				$p = wc_get_product( $id );
				return $p && $p->is_visible() && $p->is_in_stock();
			} ) );

			return array_slice( $ids, 0, $limit );
		}

		/**
		 * Render the suggestion grid/list HTML.
		 *
		 * @param int[] $ids
		 * @return string
		 */
		private function render_suggestions( $ids ) {
			$s = $this->get_settings();
			ob_start();
			?>
			<div class="sz-xs-list sz-xs-list--<?php echo esc_attr( $s['layout'] ); ?>">
				<?php foreach ( $ids as $id ) :
					$p = wc_get_product( $id );
					if ( ! $p ) continue;
					$thumb_id = $p->get_image_id();
					$on_sale  = $p->is_on_sale();
					$rating   = (float) $p->get_average_rating();
					$reviews  = (int) $p->get_review_count();
					$discount = '';
					if ( $on_sale && $s['show_discount_badge'] ) {
						$reg = (float) $p->get_regular_price();
						$sal = (float) $p->get_sale_price();
						if ( $reg > 0 && $sal > 0 && $sal < $reg ) {
							$discount = (int) round( ( ( $reg - $sal ) / $reg ) * 100 );
						}
					}
					?>
					<article class="sz-xs-item" data-product-id="<?php echo esc_attr( $id ); ?>">
						<?php if ( $discount ) : ?>
							<span class="sz-xs-item__discount">-<?php echo (int) $discount; ?>%</span>
						<?php endif; ?>

						<a class="sz-xs-item__thumb" href="<?php echo esc_url( get_permalink( $id ) ); ?>">
							<?php if ( $thumb_id ) {
								echo wp_get_attachment_image( $thumb_id, 'woocommerce_thumbnail', false, array( 'alt' => esc_attr( $p->get_name() ), 'loading' => 'lazy' ) );
							} ?>
						</a>

						<div class="sz-xs-item__body">
							<a class="sz-xs-item__name" href="<?php echo esc_url( get_permalink( $id ) ); ?>">
								<?php echo esc_html( wp_trim_words( $p->get_name(), 6 ) ); ?>
							</a>

							<?php if ( $s['show_price'] ) : ?>
								<div class="sz-xs-item__price"><?php echo wp_kses_post( $p->get_price_html() ); ?></div>
							<?php endif; ?>

							<?php if ( $s['show_rating'] && $reviews > 0 ) : ?>
								<div class="sz-xs-item__rating"><?php echo wc_get_rating_html( $rating, $reviews ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WC escaped ?></div>
							<?php endif; ?>

							<button
								type="button"
								class="sz-xs-item__atc"
								data-product-id="<?php echo esc_attr( $id ); ?>"
								data-product-type="<?php echo esc_attr( $p->get_type() ); ?>"
							>
								<?php echo esc_html( $s['button_text'] ); ?>
							</button>
						</div>
					</article>
				<?php endforeach; ?>
			</div>
			<?php
			return ob_get_clean();
		}

	} // end class

} // end class_exists
