<?php
/**
 * Storzen Module — Sale Notification
 *
 * Shows recent purchase popups (social proof) anchored to a corner of the page.
 * Pulls from real WooCommerce orders, generated "virtual" data, or a mix.
 *
 * Message template supports placeholders:
 *   {name}, {city}, {country}, {product}, {time}
 *
 * Architecture:
 *   • PHP fetches recent processing/completed orders (transient cached).
 *   • JSON payload is rendered inline (no separate AJAX request).
 *   • Vanilla JS cycles items at configured intervals, supports dismiss.
 *
 * Compatibility:
 *   • HPOS (uses wc_get_orders() only).
 *   • Simple + variable products (first item per order).
 *   • Guest + logged-in checkout flows.
 *   • Mobile responsive (CSS handles edge-pinning <480px).
 *
 * Privacy:
 *   • Only first name + last initial shown ("John D.").
 *   • City + country name only — no street/email/phone.
 *   • Virtual mode is GDPR-safe (no real customer data).
 *
 * @package storzen
 * @since   1.1.0
 */

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

if ( ! class_exists( 'Storzen_Module_Sale_Notification' ) ) {

	class Storzen_Module_Sale_Notification {

		/** Transient key for cached real-order payload */
		const CACHE_KEY = 'storzen_sale_notifications';

		/** Cache lifetime (30 minutes — short enough to feel fresh) */
		const CACHE_TTL = 1800;

		/** @var array Resolved settings cache (per request) */
		private $settings = null;

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

			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
			add_action( 'wp_footer',          array( $this, 'render_container' ), 20 );

			// Bust cache when an order changes status (HPOS-safe).
			add_action( 'woocommerce_new_order',                       array( $this, 'flush_cache' ) );
			add_action( 'woocommerce_order_status_processing',         array( $this, 'flush_cache' ) );
			add_action( 'woocommerce_order_status_completed',          array( $this, 'flush_cache' ) );
			add_action( 'woocommerce_order_status_changed',            array( $this, 'flush_cache' ) );
		}

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

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

			$positions  = array( 'bottom-left', 'bottom-right', 'top-left', 'top-right' );
			$sources    = array( 'real', 'virtual', 'mixed' );
			$animations = array( 'slide-up', 'slide-side', 'fade' );

			$message = (string) storzen_module_setting( 'sale_notification', 'message' );
			if ( '' === $message ) {
				$message = '{name} from {city} purchased {product}';
			}

			$position = (string) storzen_module_setting( 'sale_notification', 'position' );
			if ( ! in_array( $position, $positions, true ) ) {
				$position = 'bottom-left';
			}

			$source = (string) storzen_module_setting( 'sale_notification', 'data_source' );
			if ( ! in_array( $source, $sources, true ) ) {
				$source = 'real';
			}

			$animation = (string) storzen_module_setting( 'sale_notification', 'animation' );
			if ( ! in_array( $animation, $animations, true ) ) {
				$animation = 'slide-up';
			}

			$this->settings = array(
				'message'           => $message,
				'position'          => $position,
				'data_source'       => $source,
				'animation'         => $animation,
				'lookback_days'     => max( 1, (int) storzen_module_setting( 'sale_notification', 'lookback_days' ) ),
				'max_notifications' => max( 1, (int) storzen_module_setting( 'sale_notification', 'max_notifications' ) ),
				'first_delay'       => max( 0, (int) storzen_module_setting( 'sale_notification', 'first_delay' ) ),
				'display_duration'  => max( 2, (int) storzen_module_setting( 'sale_notification', 'display_duration' ) ),
				'between_delay'     => max( 2, (int) storzen_module_setting( 'sale_notification', 'between_delay' ) ),
				'show_avatar'       => (bool) storzen_module_setting( 'sale_notification', 'show_avatar' ),
				'show_time'         => (bool) storzen_module_setting( 'sale_notification', 'show_time' ),
				'show_close'        => (bool) storzen_module_setting( 'sale_notification', 'show_close' ),
			);

			return $this->settings;
		}

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

		public function enqueue_assets() {
			if ( is_admin() ) {
				return;
			}
			// Don't show on the order-received page (we shouldn't push sales on a thank-you page).
			if ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) {
				return;
			}
			if ( class_exists( 'Elementor\Storzen_Archive_Grid' ) && false === \Elementor\Storzen_Archive_Grid::is_page_module_enabled( 'sale_notification' ) ) {
				return;
			}

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

			wp_enqueue_script(
				'storzen-sale-notification',
				STORZEN_ROOT_URL . 'assets/js/modules/sale-notification.js',
				array(),
				STORZEN_VERSION,
				true
			);
		}

		// ─────────────────────────────────────────────────────────────────
		// Data sources
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Get notification list from real WC orders (cached).
		 *
		 * @return array
		 */
		private function get_real_sales() {
			$cached = get_transient( self::CACHE_KEY );
			if ( false !== $cached && is_array( $cached ) ) {
				return $cached;
			}

			$settings = $this->get_settings();

			$orders = wc_get_orders(
				array(
					'status'     => array( 'wc-processing', 'wc-completed' ),
					'limit'      => (int) $settings['max_notifications'],
					'date_after' => gmdate( 'Y-m-d', strtotime( '-' . (int) $settings['lookback_days'] . ' days' ) ),
					'orderby'    => 'date',
					'order'      => 'DESC',
					'return'     => 'objects',
				)
			);

			if ( empty( $orders ) ) {
				set_transient( self::CACHE_KEY, array(), self::CACHE_TTL );
				return array();
			}

			$notifications = array();

			foreach ( $orders as $order ) {
				if ( ! $order instanceof WC_Order ) {
					continue;
				}

				$items = $order->get_items();
				if ( empty( $items ) ) {
					continue;
				}

				$first_item = reset( $items );
				if ( ! $first_item ) {
					continue;
				}

				/** @var WC_Order_Item_Product $first_item */
				$product = $first_item->get_product();
				if ( ! $product instanceof WC_Product ) {
					continue;
				}

				$first_name = $order->get_billing_first_name();
				if ( '' === $first_name ) {
					$first_name = __( 'Someone', 'storzen' );
				}

				$last = $order->get_billing_last_name();
				if ( '' !== $last ) {
					$initial = function_exists( 'mb_substr' )
						? mb_strtoupper( mb_substr( $last, 0, 1 ) )
						: strtoupper( substr( $last, 0, 1 ) );
					$buyer_name = trim( $first_name . ' ' . $initial . '.' );
				} else {
					$buyer_name = $first_name;
				}

				$image_url = '';
				$img_id    = $product->get_image_id();
				if ( $img_id ) {
					$image_url = (string) wp_get_attachment_image_url( $img_id, 'thumbnail' );
				}

				$ts = $order->get_date_created() ? $order->get_date_created()->getTimestamp() : time();

				$notifications[] = array(
					'name'    => sanitize_text_field( $buyer_name ),
					'city'    => sanitize_text_field( (string) $order->get_billing_city() ),
					'country' => $this->get_country_name( (string) $order->get_billing_country() ),
					'product' => sanitize_text_field( $product->get_name() ),
					'image'   => esc_url_raw( $image_url ),
					'url'     => esc_url_raw( (string) $product->get_permalink() ),
					'time'    => $this->time_ago( (int) $ts ),
				);
			}

			set_transient( self::CACHE_KEY, $notifications, self::CACHE_TTL );

			return $notifications;
		}

		/**
		 * Build virtual/synthetic notifications from generic names + actual products.
		 *
		 * @return array
		 */
		private function get_virtual_sales() {
			$settings = $this->get_settings();

			$names = array(
				'John D.', 'Sarah W.', 'Mike T.', 'Emma R.', 'David L.', 'Linda M.',
				'Chris P.', 'Rachel B.', 'Tom H.', 'Anna K.', 'Robert S.', 'Maria G.',
				'James F.', 'Sofia A.', 'Daniel C.', 'Olivia N.',
			);
			$cities = array(
				'New York', 'London', 'Toronto', 'Sydney', 'Berlin', 'Paris',
				'Madrid', 'Amsterdam', 'Dubai', 'Singapore', 'Tokyo', 'Mumbai',
				'São Paulo', 'Mexico City', 'Cape Town', 'Stockholm',
			);
			$countries = array(
				'United States', 'United Kingdom', 'Canada', 'Australia', 'Germany', 'France',
				'Spain', 'Netherlands', 'United Arab Emirates', 'Singapore', 'Japan', 'India',
				'Brazil', 'Mexico', 'South Africa', 'Sweden',
			);
			$hours_pool = array( 1, 2, 3, 4, 6, 8, 12, 18, 24, 36 );

			$products = wc_get_products(
				array(
					'limit'        => 20,
					'status'       => 'publish',
					'stock_status' => 'instock',
					'return'       => 'objects',
				)
			);

			if ( empty( $products ) ) {
				return array();
			}

			$notifications = array();
			$count         = min( (int) $settings['max_notifications'], 16 );

			for ( $i = 0; $i < $count; $i++ ) {
				$geo_idx = array_rand( $cities );
				$product = $products[ array_rand( $products ) ];
				$hours   = $hours_pool[ array_rand( $hours_pool ) ];

				$img_url = '';
				$img_id  = $product->get_image_id();
				if ( $img_id ) {
					$img_url = (string) wp_get_attachment_image_url( $img_id, 'thumbnail' );
				}

				$notifications[] = array(
					'name'    => $names[ array_rand( $names ) ],
					'city'    => $cities[ $geo_idx ],
					'country' => $countries[ $geo_idx ],
					'product' => sanitize_text_field( $product->get_name() ),
					'image'   => esc_url_raw( $img_url ),
					'url'     => esc_url_raw( (string) $product->get_permalink() ),
					'time'    => sprintf(
						/* translators: %d: number of hours */
						_n( '%d hour ago', '%d hours ago', $hours, 'storzen' ),
						$hours
					),
				);
			}

			return $notifications;
		}

		/**
		 * Final merged notifications based on data_source.
		 *
		 * @return array
		 */
		private function get_notifications() {
			$settings = $this->get_settings();
			$source   = $settings['data_source'];

			switch ( $source ) {
				case 'virtual':
					return $this->get_virtual_sales();

				case 'mixed':
					$real    = $this->get_real_sales();
					$virtual = $this->get_virtual_sales();
					return array_slice( array_merge( $real, $virtual ), 0, (int) $settings['max_notifications'] );

				case 'real':
				default:
					return $this->get_real_sales();
			}
		}

		// ─────────────────────────────────────────────────────────────────
		// Frontend render
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Render the notification container + JSON payload in the footer.
		 */
		public function render_container() {
			if ( is_admin() ) {
				return;
			}
			if ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) {
				return;
			}
			if ( class_exists( 'Elementor\Storzen_Archive_Grid' ) && false === \Elementor\Storzen_Archive_Grid::is_page_module_enabled( 'sale_notification' ) ) {
				return;
			}

			$notifications = $this->get_notifications();
			if ( empty( $notifications ) ) {
				return;
			}

			$settings = $this->get_settings();

			$classes = array(
				'sz-sn-container',
				'sz-sn-container--' . sanitize_html_class( $settings['position'] ),
				'sz-sn-container--' . sanitize_html_class( $settings['animation'] ),
			);
			?>
			<div
				class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>"
				data-first-delay="<?php echo esc_attr( (string) $settings['first_delay'] ); ?>"
				data-duration="<?php echo esc_attr( (string) $settings['display_duration'] ); ?>"
				data-between="<?php echo esc_attr( (string) $settings['between_delay'] ); ?>"
				data-message="<?php echo esc_attr( $settings['message'] ); ?>"
				data-show-avatar="<?php echo esc_attr( $settings['show_avatar'] ? '1' : '0' ); ?>"
				data-show-time="<?php echo esc_attr( $settings['show_time'] ? '1' : '0' ); ?>"
				data-show-close="<?php echo esc_attr( $settings['show_close'] ? '1' : '0' ); ?>"
				data-max-count="<?php echo esc_attr( (string) $settings['max_notifications'] ); ?>"
				aria-live="polite"
				aria-atomic="true"
			>
				<script type="application/json" class="sz-sn-data">
					<?php
					// wp_json_encode returns a safe JSON string with proper escaping for embedding in a <script type="application/json"> tag.
					echo wp_json_encode( $notifications ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-encoded payload.
					?>
				</script>
			</div>
			<?php
		}

		// ─────────────────────────────────────────────────────────────────
		// Utilities
		// ─────────────────────────────────────────────────────────────────

		/**
		 * Convert a country code (e.g. "US") into its localized name.
		 *
		 * @param string $code Two-letter country code.
		 * @return string
		 */
		private function get_country_name( $code ) {
			if ( '' === $code ) {
				return '';
			}
			if ( function_exists( 'WC' ) && WC()->countries ) {
				$list = WC()->countries->get_countries();
				if ( isset( $list[ $code ] ) ) {
					return (string) $list[ $code ];
				}
			}
			return $code;
		}

		/**
		 * Format a Unix timestamp as a relative phrase ("3 hours ago").
		 *
		 * @param int $timestamp Unix timestamp.
		 * @return string
		 */
		private function time_ago( $timestamp ) {
			$now  = (int) current_time( 'timestamp' );
			$diff = $now - (int) $timestamp;

			if ( $diff < 60 ) {
				return __( 'just now', 'storzen' );
			}
			if ( $diff < HOUR_IN_SECONDS ) {
				$m = (int) floor( $diff / MINUTE_IN_SECONDS );
				/* translators: %d: minutes */
				return sprintf( _n( '%d minute ago', '%d minutes ago', $m, 'storzen' ), $m );
			}
			if ( $diff < DAY_IN_SECONDS ) {
				$h = (int) floor( $diff / HOUR_IN_SECONDS );
				/* translators: %d: hours */
				return sprintf( _n( '%d hour ago', '%d hours ago', $h, 'storzen' ), $h );
			}
			$d = (int) floor( $diff / DAY_IN_SECONDS );
			/* translators: %d: days */
			return sprintf( _n( '%d day ago', '%d days ago', $d, 'storzen' ), $d );
		}

		/**
		 * Delete the cached payload (called on new/changed orders).
		 */
		public function flush_cache() {
			delete_transient( self::CACHE_KEY );
		}

	} // end class

} // end class_exists
