<?php
/**
 * Wishlist Module.
 *
 * Lets customers save products for later, share wishlists via URL,
 * and view/manage their wishlist via a shortcode-powered page.
 *
 * @package JWP_STK
 */

namespace JWP_STK\Modules\Wishlist;

use JWP_STK\Modules\Base_Module;

defined( 'ABSPATH' ) || exit;

/**
 * Module class.
 */
class Module extends Base_Module {

	/**
	 * Cookie name for guest wishlists.
	 *
	 * @var string
	 */
	const COOKIE_NAME = 'jwp_stk_wishlist';

	/**
	 * User meta key for logged-in wishlists.
	 *
	 * @var string
	 */
	const META_KEY = '_jwp_stk_wishlist';

	/**
	 * Share query arg name.
	 *
	 * @var string
	 */
	const SHARE_ARG = 'jwp_share';

	/**
	 * Get the module slug.
	 *
	 * @return string
	 */
	public function get_slug() {
		return 'wishlist';
	}

	/**
	 * Get default settings.
	 *
	 * @return array
	 */
	public function get_defaults() {
		return array(
			// Controls.
			'wishlist_page_id'         => 0,
			'wishlist_page_injection'  => 'auto',
			'show_on_my_account'       => true,
			'button_integration'       => 'hooks',
			'show_on_shop'             => true,
			'show_on_product'          => true,
			'button_position_shop'     => 'after_add_to_cart',
			'button_position_product'  => 'before_add_to_cart',
			'redirect_to_wishlist'     => false,
			// Customize button.
			'button_label_add'         => 'Add to Wishlist',
			'button_label_added'       => 'Added to Wishlist',
			'button_style'             => 'icon_text',
			'button_icon'              => 'heart',
			'button_size'              => 'normal',
			'button_type'              => 'filled',
			'button_style_mode'        => 'custom',
			'button_align_shop'        => 'center',
			'button_align_product'     => 'left',
			'button_bg_color'          => '#333333',
			'button_text_color'        => '#ffffff',
			'button_hover_bg_color'    => '#555555',
			'button_hover_text_color'  => '#ffffff',
			'button_active_bg_color'   => '',
			'button_active_text_color' => '',
			'button_padding'           => '',
			'button_border_radius'     => '',
		);
	}

	/**
	 * Sanitize a setting value.
	 *
	 * @param string $key   Setting key.
	 * @param mixed  $value Setting value.
	 * @return mixed
	 */
	public function sanitize_setting( $key, $value ) {
		$color_keys = array(
			'button_bg_color',
			'button_text_color',
			'button_hover_bg_color',
			'button_hover_text_color',
			'button_active_bg_color',
			'button_active_text_color',
		);

		if ( in_array( $key, $color_keys, true ) ) {
			if ( '' === $value || null === $value ) {
				return '';
			}
			$sanitized = sanitize_hex_color( $value );
			return $sanitized ? $sanitized : '';
		}

		if ( in_array( $key, array( 'button_padding', 'button_border_radius' ), true ) ) {
			if ( '' === (string) $value ) {
				return '';
			}
			$n = (int) $value;
			return $n >= 0 ? (string) $n : '';
		}

		if ( 'wishlist_page_id' === $key ) {
			return absint( $value );
		}

		if ( 'wishlist_page_injection' === $key ) {
			return in_array( $value, array( 'auto', 'manual' ), true ) ? $value : 'auto';
		}

		if ( 'show_on_my_account' === $key ) {
			return (bool) $value;
		}

		if ( in_array( $key, array( 'button_align_shop', 'button_align_product' ), true ) ) {
			return in_array( $value, array( 'left', 'center', 'right' ), true ) ? $value : ( 'button_align_shop' === $key ? 'center' : 'left' );
		}

		if ( 'button_type' === $key ) {
			return in_array( $value, array( 'filled', 'outline' ), true ) ? $value : 'filled';
		}

		if ( 'button_style_mode' === $key ) {
			return in_array( $value, array( 'theme', 'custom' ), true ) ? $value : 'custom';
		}

		return parent::sanitize_setting( $key, $value );
	}

	/**
	 * Initialise the module.
	 *
	 * @return void
	 */
	public function init() {
		// Shortcode — always register so the page renders.
		add_shortcode( 'storetoolkit_wishlist', array( $this, 'render_shortcode' ) );

		// Wishlist button shortcode — for manual placement in templates.
		add_shortcode( 'storetoolkit_add_to_wishlist', array( $this, 'render_button_shortcode' ) );

		// My Account tab.
		if ( $this->get_setting( 'show_on_my_account' ) ) {
			add_action( 'init', array( $this, 'register_my_account_endpoint' ) );
			add_filter( 'woocommerce_account_menu_items', array( $this, 'add_my_account_menu_item' ) );
			add_action( 'woocommerce_account_wishlist_endpoint', array( $this, 'render_my_account_endpoint' ) );
		}

		if ( ! is_admin() ) {
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
			$this->register_button_hooks();
			if ( 'auto' === $this->get_setting( 'wishlist_page_injection' ) ) {
				add_filter( 'the_content', array( $this, 'inject_wishlist_page_content' ) );
			}
		}

		// AJAX — logged-in users.
		add_action( 'wp_ajax_jwp_stk_wishlist_toggle', array( $this, 'ajax_toggle' ) );
		add_action( 'wp_ajax_jwp_stk_wishlist_get', array( $this, 'ajax_get' ) );
		add_action( 'wp_ajax_jwp_stk_wishlist_get_share_url', array( $this, 'ajax_get_share_url' ) );

		// AJAX — guests.
		add_action( 'wp_ajax_nopriv_jwp_stk_wishlist_toggle', array( $this, 'ajax_toggle' ) );
		add_action( 'wp_ajax_nopriv_jwp_stk_wishlist_get', array( $this, 'ajax_get' ) );
		add_action( 'wp_ajax_nopriv_jwp_stk_wishlist_get_share_url', array( $this, 'ajax_get_share_url' ) );

		// Merge guest wishlist into user account on login.
		add_action( 'wp_login', array( $this, 'merge_guest_wishlist_on_login' ), 10, 2 );
	}

	// ──────────────────────────────────────────────────────────────────
	// My Account endpoint
	// ──────────────────────────────────────────────────────────────────

	/**
	 * Register the wishlist rewrite endpoint for My Account.
	 *
	 * Must be hooked to WordPress 'init' so rewrite rules are available.
	 * After enabling, the user may need to flush permalinks once (Settings → Permalinks → Save).
	 *
	 * @return void
	 */
	public function register_my_account_endpoint() {
		add_rewrite_endpoint( 'wishlist', EP_ROOT | EP_PAGES );
	}

	/**
	 * Add Wishlist entry to the My Account navigation menu.
	 *
	 * Inserted before the logout link.
	 *
	 * @param array $items Menu items.
	 * @return array
	 */
	public function add_my_account_menu_item( array $items ) {
		$logout = isset( $items['customer-logout'] ) ? $items['customer-logout'] : null;
		unset( $items['customer-logout'] );

		$items['wishlist'] = __( 'Wishlist', 'jetix-store-toolkit' );

		if ( null !== $logout ) {
			$items['customer-logout'] = $logout;
		}

		return $items;
	}

	/**
	 * Render the wishlist content on the My Account → Wishlist endpoint page.
	 *
	 * @return void
	 */
	public function render_my_account_endpoint() {
		echo do_shortcode( '[storetoolkit_wishlist]' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Register hooks for button injection positions.
	 *
	 * Only runs when integration mode is 'hooks'.
	 *
	 * @return void
	 */
	private function register_button_hooks() {
		// Only auto-inject via hooks when the integration mode is set to 'hooks'.
		if ( 'hooks' !== $this->get_setting( 'button_integration' ) ) {
			return;
		}

		if ( $this->get_setting( 'show_on_shop' ) ) {
			$this->register_shop_button_hook();
		}

		if ( $this->get_setting( 'show_on_product' ) ) {
			$this->register_product_button_hook();
		}
	}

	/**
	 * Register the correct shop/archive hook for the configured position.
	 *
	 * @return void
	 */
	private function register_shop_button_hook() {
		$position = $this->get_setting( 'button_position_shop' );

		switch ( $position ) {
			case 'before_product_image':
				add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'render_shop_button' ), 5 );
				break;
			case 'after_product_image':
				add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'render_shop_button' ), 15 );
				break;
			case 'before_product_title':
				add_action( 'woocommerce_shop_loop_item_title', array( $this, 'render_shop_button' ), 5 );
				break;
			case 'after_product_title':
				add_action( 'woocommerce_after_shop_loop_item_title', array( $this, 'render_shop_button' ), 5 );
				break;
			case 'before_price':
				add_action( 'woocommerce_after_shop_loop_item_title', array( $this, 'render_shop_button' ), 8 );
				break;
			case 'after_price':
				add_action( 'woocommerce_after_shop_loop_item_title', array( $this, 'render_shop_button' ), 15 );
				break;
			case 'before_add_to_cart':
				add_action( 'woocommerce_after_shop_loop_item', array( $this, 'render_shop_button' ), 9 );
				break;
			case 'after_add_to_cart':
			default:
				add_action( 'woocommerce_after_shop_loop_item', array( $this, 'render_shop_button' ), 11 );
				break;
		}
	}

	/**
	 * Register the correct single product hook for the configured position.
	 *
	 * @return void
	 */
	private function register_product_button_hook() {
		$position = $this->get_setting( 'button_position_product' );

		switch ( $position ) {
			case 'before_product_title':
				add_action( 'woocommerce_single_product_summary', array( $this, 'render_product_button' ), 4 );
				break;
			case 'after_product_title':
				add_action( 'woocommerce_single_product_summary', array( $this, 'render_product_button' ), 7 );
				break;
			case 'before_price':
				add_action( 'woocommerce_single_product_summary', array( $this, 'render_product_button' ), 9 );
				break;
			case 'after_price':
				add_action( 'woocommerce_single_product_summary', array( $this, 'render_product_button' ), 12 );
				break;
			case 'after_add_to_cart':
				add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_product_button' ), 20 );
				break;
			case 'before_add_to_cart':
			default:
				add_action( 'woocommerce_before_add_to_cart_button', array( $this, 'render_product_button' ), 20 );
				break;
		}
	}

	/**
	 * Enqueue frontend assets.
	 *
	 * @return void
	 */
	public function enqueue_assets() {
		wp_enqueue_style(
			'jwp-stk-wishlist',
			JWP_STK_PLUGIN_URL . 'assets/css/wishlist.css',
			array(),
			JWP_STK_VERSION
		);

		wp_enqueue_script(
			'jwp-stk-wishlist',
			JWP_STK_PLUGIN_URL . 'assets/js/wishlist.js',
			array( 'jquery' ),
			JWP_STK_VERSION,
			true
		);

		$wishlist_page_id  = (int) $this->get_setting( 'wishlist_page_id' );
		$wishlist_page_url = $wishlist_page_id ? get_permalink( $wishlist_page_id ) : '';

		wp_localize_script(
			'jwp-stk-wishlist',
			'jwpStkWishlist',
			array(
				'ajaxUrl'         => admin_url( 'admin-ajax.php' ),
				'nonce'           => wp_create_nonce( 'jwp_stk_wishlist' ),
				'wishlistPageUrl' => $wishlist_page_url ? esc_url( $wishlist_page_url ) : '',
				'redirectOnAdd'   => (bool) $this->get_setting( 'redirect_to_wishlist' ),
				'labelAdd'        => esc_html( $this->get_setting( 'button_label_add' ) ),
				'labelAdded'      => esc_html( $this->get_setting( 'button_label_added' ) ),
				'currentWishlist' => $this->get_wishlist_ids(),
				'shareArg'        => self::SHARE_ARG,
			)
		);
	}

	/**
	 * Render the wishlist button on shop/archive pages.
	 *
	 * @return void
	 */
	public function render_shop_button() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}
		$this->render_button( $product->get_id(), 'shop' );
	}

	/**
	 * Render the wishlist button on single product pages.
	 *
	 * @return void
	 */
	public function render_product_button() {
		global $product;
		if ( ! $product instanceof \WC_Product ) {
			return;
		}
		$this->render_button( $product->get_id(), 'product' );
	}

	/**
	 * Render the [storetoolkit_add_to_wishlist] shortcode.
	 *
	 * Can be used anywhere a product is in context. When `product_id` is
	 * omitted the global $product is used (inside a product loop / template).
	 *
	 * @param array $atts Shortcode attributes.
	 * @return string HTML output.
	 */
	public function render_button_shortcode( $atts ) {
		$atts = shortcode_atts(
			array(
				'product_id' => 0,
				'context'    => 'loop',
			),
			$atts,
			'storetoolkit_add_to_wishlist'
		);

		$product_id = absint( $atts['product_id'] );
		$context    = sanitize_key( $atts['context'] );

		if ( ! $product_id ) {
			global $product;
			if ( $product instanceof \WC_Product ) {
				$product_id = $product->get_id();
			}
		}

		if ( ! $product_id ) {
			return '';
		}

		if ( ! wp_script_is( 'jwp-stk-wishlist', 'enqueued' ) ) {
			$this->enqueue_assets();
		}

		ob_start();
		$this->render_button( $product_id, $context );
		return ob_get_clean();
	}

	/**
	 * Output the wishlist button markup.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $context    'shop' or 'product'.
	 * @return void
	 */
	private function render_button( $product_id, $context = 'shop' ) {
		$label_add   = $this->get_setting( 'button_label_add' );
		$label_added = $this->get_setting( 'button_label_added' );
		$style       = $this->get_setting( 'button_style' );
		$icon        = $this->get_setting( 'button_icon' );
		$size        = $this->get_setting( 'button_size' );
		$button_type = $this->get_setting( 'button_type' ) ? $this->get_setting( 'button_type' ) : 'filled';
		$mode        = $this->get_setting( 'button_style_mode' ) ? $this->get_setting( 'button_style_mode' ) : 'custom';
		$align       = 'shop' === $context
			? ( $this->get_setting( 'button_align_shop' ) ? $this->get_setting( 'button_align_shop' ) : 'center' )
			: ( $this->get_setting( 'button_align_product' ) ? $this->get_setting( 'button_align_product' ) : 'left' );
		$bg_color    = $this->get_setting( 'button_bg_color' );
		$text_color  = $this->get_setting( 'button_text_color' );
		$hover_bg    = $this->get_setting( 'button_hover_bg_color' );
		$hover_text  = $this->get_setting( 'button_hover_text_color' );
		$active_bg   = $this->get_setting( 'button_active_bg_color' );
		$active_text = $this->get_setting( 'button_active_text_color' );
		$padding     = $this->get_setting( 'button_padding' );
		$radius      = $this->get_setting( 'button_border_radius' );

		$allowed_styles = array( 'icon_text', 'icon', 'text' );
		$allowed_icons  = array( 'heart', 'star', 'bookmark' );
		$allowed_sizes  = array( 'small', 'normal', 'large' );
		$allowed_aligns = array( 'left', 'center', 'right' );
		$allowed_types  = array( 'filled', 'outline' );

		$style       = in_array( $style, $allowed_styles, true ) ? $style : 'icon_text';
		$icon        = in_array( $icon, $allowed_icons, true ) ? $icon : 'heart';
		$size        = in_array( $size, $allowed_sizes, true ) ? $size : 'normal';
		$align       = in_array( $align, $allowed_aligns, true ) ? $align : ( 'shop' === $context ? 'center' : 'left' );
		$button_type = in_array( $button_type, $allowed_types, true ) ? $button_type : 'filled';

		$in_wishlist = $this->is_in_wishlist( $product_id );
		$label       = $in_wishlist ? $label_added : $label_add;

		$classes = array(
			'jwp-stk-wishlist-btn',
			'jwp-stk-wl-btn--' . $style,
			'jwp-stk-wl-btn--' . $size,
			'jwp-stk-wl-btn--' . $button_type,
			'jwp-stk-wl-ctx--' . $context,
		);

		if ( $in_wishlist ) {
			$classes[] = 'is-wishlisted';
		}

		$inline_style = '';
		if ( 'theme' !== $mode ) {
			if ( $in_wishlist ) {
				if ( ! empty( $active_bg ) ) {
					$inline_style .= sprintf( 'background-color:%s;border-color:%s;', sanitize_hex_color( $active_bg ), sanitize_hex_color( $active_bg ) );
				}
				if ( ! empty( $active_text ) ) {
					$inline_style .= sprintf( 'color:%s;', sanitize_hex_color( $active_text ) );
				}
			} else {
				if ( ! empty( $bg_color ) ) {
					$inline_style .= sprintf( 'background-color:%s;border-color:%s;', sanitize_hex_color( $bg_color ), sanitize_hex_color( $bg_color ) );
				}
				if ( ! empty( $text_color ) ) {
					$inline_style .= sprintf( 'color:%s;', sanitize_hex_color( $text_color ) );
				}
			}
			if ( '' !== (string) $padding ) {
				$inline_style .= sprintf( 'padding:%dpx;', (int) $padding );
			}
			if ( '' !== (string) $radius ) {
				$inline_style .= sprintf( 'border-radius:%dpx;', (int) $radius );
			}
		}

		$wrap_vars  = array();
		$wrap_class = 'jwp-stk-wl-btn-wrap jwp-stk-wl-btn-wrap--' . $align;
		if ( 'theme' !== $mode ) {
			$wrap_class .= ' jwp-stk-wl-btn-wrap--custom';
			if ( ! empty( $hover_bg ) ) {
				$wrap_vars[] = '--jwp-wl-hover-bg:' . sanitize_hex_color( $hover_bg );
			}
			if ( ! empty( $hover_text ) ) {
				$wrap_vars[] = '--jwp-wl-hover-text:' . sanitize_hex_color( $hover_text );
			}
		}
		$wrap_style_attr = ! empty( $wrap_vars ) ? ' style="' . esc_attr( implode( ';', $wrap_vars ) ) . '"' : '';

		$icon_svg = $this->get_icon_svg( $icon );

		printf(
			'<div class="%1$s"%2$s><button type="button" class="%3$s"%4$s data-product-id="%5$d" data-label-add="%6$s" data-label-added="%7$s" data-bg="%8$s" data-text="%9$s" data-active-bg="%10$s" data-active-text="%11$s" aria-label="%12$s">%13$s%14$s</button></div>',
			esc_attr( $wrap_class ),
			$wrap_style_attr, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped attr string built with esc_attr().
			esc_attr( implode( ' ', $classes ) ),
			$inline_style ? ' style="' . esc_attr( $inline_style ) . '"' : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- conditional pre-escaped inline style.
			esc_attr( $product_id ),
			esc_attr( $label_add ),
			esc_attr( $label_added ),
			esc_attr( sanitize_hex_color( $bg_color ) ),
			esc_attr( sanitize_hex_color( $text_color ) ),
			esc_attr( sanitize_hex_color( $active_bg ) ),
			esc_attr( sanitize_hex_color( $active_text ) ),
			esc_attr( $in_wishlist ? $label_added : $label_add ),
			( 'text' !== $style ) ? wp_kses( $icon_svg, $this->get_icon_kses_tags() ) : '',
			( 'icon' !== $style ) ? '<span class="jwp-stk-wl-btn-text">' . esc_html( $label ) . '</span>' : ''
		);
	}

	/**
	 * Allowed HTML tags and attributes for SVG icon output.
	 *
	 * @return array
	 */
	private function get_icon_kses_tags() {
		return array(
			'svg'     => array(
				'class'           => array(),
				'xmlns'           => array(),
				'viewbox'         => array(),
				'fill'            => array(),
				'stroke'          => array(),
				'stroke-width'    => array(),
				'stroke-linecap'  => array(),
				'stroke-linejoin' => array(),
				'aria-hidden'     => array(),
				'width'           => array(),
				'height'          => array(),
			),
			'path'    => array(
				'd'         => array(),
				'fill'      => array(),
				'fill-rule' => array(),
				'clip-rule' => array(),
			),
			'polygon' => array(
				'points' => array(),
			),
			'line'    => array(
				'x1' => array(),
				'y1' => array(),
				'x2' => array(),
				'y2' => array(),
			),
		);
	}

	/**
	 * Return SVG markup for the given icon name.
	 *
	 * @param string $icon Icon identifier.
	 * @return string
	 */
	private function get_icon_svg( $icon ) {
		$svgs = array(
			'heart'    => '<svg class="jwp-stk-wl-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',
			'star'     => '<svg class="jwp-stk-wl-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
			'bookmark' => '<svg class="jwp-stk-wl-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>',
		);

		return isset( $svgs[ $icon ] ) ? $svgs[ $icon ] : $svgs['heart'];
	}

	// ──────────────────────────────────────────────────────────────────
	// Wishlist data layer
	// ──────────────────────────────────────────────────────────────────

	/**
	 * Get wishlist product IDs for the current user or guest.
	 *
	 * @param int|null $user_id Optional user ID override.
	 * @return int[]
	 */
	public function get_wishlist_ids( $user_id = null ) {
		if ( null === $user_id ) {
			$user_id = get_current_user_id();
		}

		if ( $user_id ) {
			$meta = get_user_meta( $user_id, self::META_KEY, true );
			return is_array( $meta ) ? array_map( 'absint', $meta ) : array();
		}

		// Guest: read cookie.
		return $this->get_guest_wishlist();
	}

	/**
	 * Get guest wishlist from cookie.
	 *
	 * @return int[]
	 */
	private function get_guest_wishlist() {
		// phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
		$cookie = isset( $_COOKIE[ self::COOKIE_NAME ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ self::COOKIE_NAME ] ) ) : '';

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

		// Sanitized above; split into positive integers.
		$ids = array_filter( array_map( 'absint', explode( ',', $cookie ) ) );

		return array_values( $ids );
	}

	/**
	 * Check whether a product is in the current wishlist.
	 *
	 * @param int $product_id Product ID.
	 * @return bool
	 */
	public function is_in_wishlist( $product_id ) {
		return in_array( (int) $product_id, $this->get_wishlist_ids(), true );
	}

	/**
	 * Add a product to the wishlist.
	 *
	 * @param int      $product_id Product ID.
	 * @param int|null $user_id    Optional user ID override (0 = guest).
	 * @return bool
	 */
	public function add_to_wishlist( $product_id, $user_id = null ) {
		$product_id = absint( $product_id );
		if ( ! $product_id || ! wc_get_product( $product_id ) ) {
			return false;
		}

		if ( null === $user_id ) {
			$user_id = get_current_user_id();
		}

		if ( $user_id ) {
			$ids = $this->get_wishlist_ids( $user_id );
			if ( ! in_array( $product_id, $ids, true ) ) {
				$ids[] = $product_id;
				update_user_meta( $user_id, self::META_KEY, $ids );
			}
			return true;
		}

		// Guest.
		$ids = $this->get_guest_wishlist();
		if ( ! in_array( $product_id, $ids, true ) ) {
			$ids[] = $product_id;
		}
		$this->set_guest_cookie( $ids );

		return true;
	}

	/**
	 * Remove a product from the wishlist.
	 *
	 * @param int      $product_id Product ID.
	 * @param int|null $user_id    Optional user ID override.
	 * @return bool
	 */
	public function remove_from_wishlist( $product_id, $user_id = null ) {
		$product_id = absint( $product_id );

		if ( null === $user_id ) {
			$user_id = get_current_user_id();
		}

		if ( $user_id ) {
			$ids = $this->get_wishlist_ids( $user_id );
			$ids = array_values( array_diff( $ids, array( $product_id ) ) );
			update_user_meta( $user_id, self::META_KEY, $ids );
			return true;
		}

		// Guest.
		$ids = $this->get_guest_wishlist();
		$ids = array_values( array_diff( $ids, array( $product_id ) ) );
		$this->set_guest_cookie( $ids );

		return true;
	}

	/**
	 * Write the guest wishlist cookie.
	 *
	 * The cookie value is a comma-separated list of product IDs, set
	 * to expire in 30 days. Using httponly=false so JS can also read it
	 * for optimistic UI updates.
	 *
	 * @param int[] $ids Product IDs.
	 * @return void
	 */
	private function set_guest_cookie( array $ids ) {
		$value   = implode( ',', array_map( 'absint', $ids ) );
		$expires = time() + ( 30 * DAY_IN_SECONDS );

		// Only set if headers not sent (AJAX context).
		if ( ! headers_sent() ) {
			setcookie( self::COOKIE_NAME, $value, $expires, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false );
		}

		// Ensure in-request reads see the updated value.
		$_COOKIE[ self::COOKIE_NAME ] = $value; // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
	}

	/**
	 * Build a shareable URL for the current user's/guest's wishlist.
	 *
	 * @return string
	 */
	public function get_share_url() {
		$page_id = (int) $this->get_setting( 'wishlist_page_id' );
		$base    = $page_id ? get_permalink( $page_id ) : home_url( '/' );
		$ids     = $this->get_wishlist_ids();

		if ( empty( $ids ) ) {
			return '';
		}

		$token = base64_encode( implode( ',', $ids ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- used for readable share tokens, not security
		return add_query_arg( self::SHARE_ARG, rawurlencode( $token ), $base );
	}

	/**
	 * Decode a share token from a URL query arg.
	 *
	 * @param string $token Raw token from URL.
	 * @return int[]
	 */
	public static function decode_share_token( $token ) {
		$decoded = base64_decode( rawurldecode( $token ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		if ( false === $decoded ) {
			return array();
		}

		return array_filter( array_map( 'absint', explode( ',', sanitize_text_field( $decoded ) ) ) );
	}

	/**
	 * Merge the guest wishlist into user account after login.
	 *
	 * @param string   $user_login Username.
	 * @param \WP_User $user       User object.
	 * @return void
	 */
	public function merge_guest_wishlist_on_login( $user_login, $user ) {
		$guest_ids = $this->get_guest_wishlist();
		if ( empty( $guest_ids ) ) {
			return;
		}

		$user_ids = $this->get_wishlist_ids( $user->ID );
		$merged   = array_values( array_unique( array_merge( $user_ids, $guest_ids ) ) );
		update_user_meta( $user->ID, self::META_KEY, $merged );

		// Clear guest cookie.
		if ( ! headers_sent() ) {
			setcookie( self::COOKIE_NAME, '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false );
		}
		unset( $_COOKIE[ self::COOKIE_NAME ] ); // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE
	}

	// ──────────────────────────────────────────────────────────────────
	// AJAX handlers
	// ──────────────────────────────────────────────────────────────────

	/**
	 * AJAX: toggle a product in the wishlist.
	 *
	 * @return void
	 */
	public function ajax_toggle() {
		check_ajax_referer( 'jwp_stk_wishlist', 'nonce' );

		$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;

		if ( ! $product_id ) {
			wp_send_json_error( array( 'message' => __( 'Invalid product.', 'jetix-store-toolkit' ) ) );
		}

		if ( $this->is_in_wishlist( $product_id ) ) {
			$this->remove_from_wishlist( $product_id );
			$in_wishlist = false;
		} else {
			$this->add_to_wishlist( $product_id );
			$in_wishlist = true;
		}

		$redirect_url = '';
		if ( $in_wishlist && $this->get_setting( 'redirect_to_wishlist' ) ) {
			$page_id = (int) $this->get_setting( 'wishlist_page_id' );
			if ( $page_id ) {
				$redirect_url = get_permalink( $page_id );
			}
		}

		wp_send_json_success(
			array(
				'in_wishlist'  => $in_wishlist,
				'ids'          => $this->get_wishlist_ids(),
				'redirect_url' => $redirect_url ? esc_url( $redirect_url ) : '',
				'label'        => $in_wishlist ? $this->get_setting( 'button_label_added' ) : $this->get_setting( 'button_label_add' ),
			)
		);
	}

	/**
	 * AJAX: return the current wishlist IDs.
	 *
	 * @return void
	 */
	public function ajax_get() {
		check_ajax_referer( 'jwp_stk_wishlist', 'nonce' );

		wp_send_json_success(
			array(
				'ids' => $this->get_wishlist_ids(),
			)
		);
	}

	/**
	 * AJAX: return a shareable URL for the current wishlist.
	 *
	 * @return void
	 */
	public function ajax_get_share_url() {
		check_ajax_referer( 'jwp_stk_wishlist', 'nonce' );

		$url = $this->get_share_url();

		if ( ! $url ) {
			wp_send_json_error( array( 'message' => __( 'Your wishlist is empty.', 'jetix-store-toolkit' ) ) );
		}

		wp_send_json_success( array( 'url' => $url ) );
	}

	// ──────────────────────────────────────────────────────────────────
	// Wishlist page — automatic content injection
	// ──────────────────────────────────────────────────────────────────

	/**
	 * Replace page content with the wishlist when on the configured wishlist page.
	 *
	 * Only fires when `wishlist_page_injection` is set to `'auto'`.
	 *
	 * @param string $content Original page content.
	 * @return string Filtered content.
	 */
	public function inject_wishlist_page_content( $content ) {
		$page_id = (int) $this->get_setting( 'wishlist_page_id' );
		if ( ! $page_id ) {
			return $content;
		}

		if ( ! is_page( $page_id ) || ! in_the_loop() || ! is_main_query() ) {
			return $content;
		}

		return $this->render_shortcode( array() );
	}

	// ──────────────────────────────────────────────────────────────────
	// Shortcode
	// ──────────────────────────────────────────────────────────────────

	/**
	 * Render the [storetoolkit_wishlist] shortcode.
	 *
	 * Displays the current user's (or guest's) wishlist table, or a
	 * read-only shared wishlist when the `jwp_share` query arg is present.
	 *
	 * @param array $atts Shortcode attributes.
	 * @return string HTML output.
	 */
	public function render_shortcode( $atts ) {
		$atts = shortcode_atts( array(), $atts, 'storetoolkit_wishlist' );

		// Enqueue assets if not already queued.
		if ( ! wp_script_is( 'jwp-stk-wishlist', 'enqueued' ) ) {
			$this->enqueue_assets();
		}

		// Check for shared wishlist.
		$share_token = isset( $_GET[ self::SHARE_ARG ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::SHARE_ARG ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$is_shared   = ! empty( $share_token );

		if ( $is_shared ) {
			$product_ids = self::decode_share_token( $share_token );
		} else {
			$product_ids = $this->get_wishlist_ids();
		}

		ob_start();
		$this->render_wishlist_table( $product_ids, $is_shared );
		return ob_get_clean();
	}

	/**
	 * Output the wishlist table HTML.
	 *
	 * @param int[] $product_ids Product IDs to display.
	 * @param bool  $is_shared   Whether this is a shared (read-only) view.
	 * @return void
	 */
	private function render_wishlist_table( array $product_ids, $is_shared = false ) {
		$page_id = (int) $this->get_setting( 'wishlist_page_id' );
		?>
		<div class="jwp-stk-wishlist-wrap" data-is-shared="<?php echo $is_shared ? '1' : '0'; ?>">
			<?php if ( $is_shared ) : ?>
				<p class="jwp-stk-wishlist-shared-notice">
					<?php esc_html_e( 'You are viewing a shared wishlist.', 'jetix-store-toolkit' ); ?>
				</p>
			<?php endif; ?>

			<?php
			/**
			 * Fires before the wishlist table (or empty-state notice).
			 *
			 * Used by the Social Sharing module to inject sharing buttons.
			 *
			 * @param int[] $product_ids Product IDs in the wishlist.
			 * @param bool  $is_shared   Whether this is a shared/read-only view.
			 */
			do_action( 'jwp_stk_before_wishlist_table', $product_ids, $is_shared );
			?>

			<?php if ( empty( $product_ids ) ) : ?>
				<p class="jwp-stk-wishlist-empty">
					<?php esc_html_e( 'Your wishlist is empty.', 'jetix-store-toolkit' ); ?>
					<?php if ( ! $is_shared ) : ?>
						<a href="<?php echo esc_url( wc_get_page_permalink( 'shop' ) ); ?>">
							<?php esc_html_e( 'Continue shopping', 'jetix-store-toolkit' ); ?>
						</a>
					<?php endif; ?>
				</p>
			<?php else : ?>
				<table class="jwp-stk-wishlist-table">
					<thead>
						<tr>
							<th class="jwp-stk-wl-col-image"><?php esc_html_e( 'Product', 'jetix-store-toolkit' ); ?></th>
							<th class="jwp-stk-wl-col-name"></th>
							<th class="jwp-stk-wl-col-price"><?php esc_html_e( 'Price', 'jetix-store-toolkit' ); ?></th>
							<th class="jwp-stk-wl-col-stock"><?php esc_html_e( 'Stock', 'jetix-store-toolkit' ); ?></th>
							<th class="jwp-stk-wl-col-actions"><?php esc_html_e( 'Actions', 'jetix-store-toolkit' ); ?></th>
							<?php if ( ! $is_shared ) : ?>
								<th class="jwp-stk-wl-col-remove"></th>
							<?php endif; ?>
						</tr>
					</thead>
					<tbody>
					<?php foreach ( $product_ids as $product_id ) : ?>
						<?php
						$product = wc_get_product( $product_id );
						if ( ! $product ) {
							continue;
						}
						?>
						<tr class="jwp-stk-wl-row" data-product-id="<?php echo esc_attr( $product_id ); ?>">
							<td class="jwp-stk-wl-col-image">
								<a href="<?php echo esc_url( $product->get_permalink() ); ?>">
									<?php echo wp_kses_post( $product->get_image( 'woocommerce_thumbnail' ) ); ?>
								</a>
							</td>
							<td class="jwp-stk-wl-col-name">
								<a href="<?php echo esc_url( $product->get_permalink() ); ?>">
									<?php echo esc_html( $product->get_name() ); ?>
								</a>
							</td>
							<td class="jwp-stk-wl-col-price">
								<?php echo wp_kses_post( $product->get_price_html() ); ?>
							</td>
							<td class="jwp-stk-wl-col-stock">
								<?php echo wp_kses_post( wc_get_stock_html( $product ) ); ?>
							</td>
							<td class="jwp-stk-wl-col-actions">
								<?php if ( $product->is_purchasable() && $product->is_in_stock() ) : ?>
									<?php if ( $product->is_type( 'simple' ) ) : ?>
										<a href="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
											class="button alt add_to_cart_button ajax_add_to_cart jwp-stk-wl-add-to-cart"
											data-product_id="<?php echo esc_attr( $product->get_id() ); ?>"
											data-product_sku="<?php echo esc_attr( $product->get_sku() ); ?>"
											<?php // translators: %s is the product name. ?>
										aria-label="<?php echo esc_attr( sprintf( __( 'Add %s to cart', 'jetix-store-toolkit' ), $product->get_name() ) ); ?>">
											<?php esc_html_e( 'Add to Cart', 'jetix-store-toolkit' ); ?>
										</a>
									<?php else : ?>
										<a href="<?php echo esc_url( $product->get_permalink() ); ?>" class="button alt">
											<?php echo esc_html( $product->add_to_cart_text() ); ?>
										</a>
									<?php endif; ?>
								<?php else : ?>
									<span class="jwp-stk-wl-unavailable">
										<?php esc_html_e( 'Unavailable', 'jetix-store-toolkit' ); ?>
									</span>
								<?php endif; ?>
							</td>
							<?php if ( ! $is_shared ) : ?>
								<td class="jwp-stk-wl-col-remove">
									<button type="button"
											class="jwp-stk-wl-remove-btn"
											data-product-id="<?php echo esc_attr( $product_id ); ?>"												<?php // translators: %s is the product name. ?>											aria-label="<?php echo esc_attr( sprintf( __( 'Remove %s from wishlist', 'jetix-store-toolkit' ), $product->get_name() ) ); ?>">
										<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
									</button>
								</td>
							<?php endif; ?>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>

				<?php if ( ! $is_shared && $page_id ) : ?>
					<div class="jwp-stk-wl-footer">
						<button type="button" class="button jwp-stk-wl-share-btn">
							<?php esc_html_e( 'Share Wishlist', 'jetix-store-toolkit' ); ?>
						</button>
						<div class="jwp-stk-wl-share-url-wrap" style="display:none;">
							<input type="text" class="jwp-stk-wl-share-url" readonly aria-label="<?php esc_attr_e( 'Shareable wishlist URL', 'jetix-store-toolkit' ); ?>" />
							<button type="button" class="button jwp-stk-wl-copy-url">
								<?php esc_html_e( 'Copy', 'jetix-store-toolkit' ); ?>
							</button>
						</div>
					</div>
				<?php endif; ?>
			<?php endif; ?>

			<?php
			/**
			 * Fires after the wishlist table (or empty-state notice).
			 *
			 * Used by the Social Sharing module to inject sharing buttons.
			 *
			 * @param int[] $product_ids Product IDs in the wishlist.
			 * @param bool  $is_shared   Whether this is a shared/read-only view.
			 */
			do_action( 'jwp_stk_after_wishlist_table', $product_ids, $is_shared );
			?>
		</div>
		<?php
	}
}
