<?php
/**
 * Frontend hooks and rendering.
 *
 * @package SKYNSMFA_Smart_Favorites
 */

declare( strict_types=1 );

namespace SKYNSMFA\Wishlist;

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


final class Frontend {
	/**
	 * Singleton instance.
	 *
	 * @var Frontend|null
	 */
	private static $instance = null;

	/**
	 * Get singleton instance.
	 *
	 * @return Frontend
	 */
	public static function instance(): Frontend {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
		add_filter( 'query_vars', array( $this, 'register_query_vars' ) );
		add_filter( 'wp_robots', array( $this, 'filter_wp_robots' ), 10, 2 );
		add_action( 'woocommerce_before_add_to_cart_form', array( $this, 'render_single_button_before_form' ), 15 );
		add_action( 'woocommerce_before_add_to_cart_button', array( $this, 'render_single_button_before' ), 10 );
		add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_single_button_after' ) );
		add_action( 'woocommerce_after_add_to_cart_form', array( $this, 'render_single_button_after_form' ), 15 );
		add_action( 'woocommerce_product_thumbnails', array( $this, 'render_single_button_over_image' ), 99 );
		add_action( 'woocommerce_after_shop_loop_item_title', array( $this, 'render_loop_button_before' ), 20 );
		add_action( 'woocommerce_after_shop_loop_item', array( $this, 'render_loop_button' ), 20 );
		add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'render_over_image_button' ), 10 );
		add_shortcode( 'skynsmfa_wishlist', array( $this, 'render_shortcode' ) );
		add_shortcode( 'skynsmfa_wishlist_icon', array( $this, 'render_icon_shortcode' ) );
		add_shortcode( 'skynsmfa_wishlist_menu_icon', array( $this, 'render_menu_icon_shortcode' ) );
	}

	/**
	 * Ensure WooCommerce session is initialized.
	 *
	 * @return void
	 */
	private function ensure_session(): void {
		if ( ! function_exists( 'WC' ) || ! WC()->session ) {
			return;
		}

		if ( ! WC()->session->has_session() ) {
			WC()->session->set_customer_session_cookie( true );
		}
	}

	/**
	 * Register public query vars (reserved for shared list URLs).
	 *
	 * @param array $vars Query vars.
	 * @return array
	 */
	public function register_query_vars( array $vars ): array {
		$vars[] = 'skynsmfa_share_list';
		return $vars;
	}

	/**
	 * Prevent indexing of utility share URLs when used.
	 *
	 * @param array $robots Robots directives.
	 * @return array
	 */
	public function filter_wp_robots( array $robots, $public = true ): array {
		unset( $public );
		$token = get_query_var( 'skynsmfa_share_list', '' );
		if ( is_string( $token ) && '' !== $token ) {
			$robots['noindex']  = true;
			$robots['nofollow'] = true;
		}

		return $robots;
	}

	/**
	 * Enqueue assets only when needed.
	 *
	 * @return void
	 */
	public function enqueue_assets(): void {

		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}

		$settings = $this->get_settings();

		$style_ver  = $this->get_asset_version( 'assets/css/skynsmfa-wishlist.css' );
		$script_ver = $this->get_asset_version( 'assets/js/skynsmfa-wishlist.js' );

		wp_register_style(
			'skynsmfa-wishlist',
			SKYNSMFA_URL . 'assets/css/skynsmfa-wishlist.css',
			array(),
			$style_ver
		);

		wp_register_script(
			'skynsmfa-wishlist',
			SKYNSMFA_URL . 'assets/js/skynsmfa-wishlist.js',
			array(),
			$script_ver,
			true
		);

		$multiple_on = 'yes' === ( $settings['skynsmfa_multiple_enable'] ?? 'yes' );
		$target_mode = (string) ( $settings['skynsmfa_add_target_mode'] ?? 'last_used' );
		if ( ! in_array( $target_mode, array( 'default', 'last_used', 'ask' ), true ) ) {
			$target_mode = 'last_used';
		}

		$this->ensure_session();
		$wishlist_data  = Session::instance()->get_data();
		$wishlist_names = Session::instance()->get_list_display_names();

		$labels = $this->get_button_labels( $settings );

		$appearance_css = $this->build_appearance_css( $settings );

		wp_enqueue_style( 'skynsmfa-wishlist' );
		if ( '' !== $appearance_css ) {
			wp_add_inline_style( 'skynsmfa-wishlist', $appearance_css );
		}

		wp_localize_script(
			'skynsmfa-wishlist',
			'skynsmfaWishlist',
			array(
				'ajax_url' => admin_url( 'admin-ajax.php' ),
				'nonce'    => wp_create_nonce( 'skynsmfa_wishlist_nonce' ),
				'wishlist' => array(
					'keys'  => $this->get_picker_list_keys( $wishlist_data ),
					'names' => $wishlist_names,
				),
				'i18n'     => array(
					'added'         => __( 'Added to wishlist.', 'skynet-smart-favorites' ),
					'removed'       => __( 'Removed from wishlist.', 'skynet-smart-favorites' ),
					'error'         => __( 'Something went wrong.', 'skynet-smart-favorites' ),
					'my_favorites'  => __( 'My favorites', 'skynet-smart-favorites' ),
					'choose_list'   => __( 'Choose a list', 'skynet-smart-favorites' ),
					'cancel'        => __( 'Cancel', 'skynet-smart-favorites' ),
					'add_to_list'   => __( 'Add to list', 'skynet-smart-favorites' ),
					'adding_to_list' => __( 'Adding…', 'skynet-smart-favorites' ),
					'create_list'   => __( 'Create new list', 'skynet-smart-favorites' ),
					'rename_list'   => __( 'Rename list', 'skynet-smart-favorites' ),
					'rename_list_prompt' => __( 'Enter a new name for this list:', 'skynet-smart-favorites' ),
					'delete_list'   => __( 'Delete list', 'skynet-smart-favorites' ),
					'delete_list_confirm' => __( 'Delete this list and all items in it? This cannot be undone.', 'skynet-smart-favorites' ),
					'list_name'     => __( 'List name', 'skynet-smart-favorites' ),
					'in_wishlist'   => $labels['added'],
					'add_label'     => $labels['add'],
					'remove_label'  => $labels['remove'],
					/* translators: %d: number of saved favorites in the menu icon label */
					'menu_icon_one' => __( 'Favorites, %d item', 'skynet-smart-favorites' ),
					/* translators: %d: number of saved favorites in the menu icon label */
					'menu_icon_many' => __( 'Favorites, %d items', 'skynet-smart-favorites' ),
					'select_variation' => __( 'Please choose product options (color, size, etc.) before adding to your wishlist.', 'skynet-smart-favorites' ),
					'select_options_cart' => __( 'Select options', 'skynet-smart-favorites' ),
				),
				'settings' => array(
					'notification_type'      => $settings['skynsmfa_notification_type'] ?? 'toast',
					'display_mode'           => $settings['skynsmfa_display_mode'] ?? 'button',
					'multiple_lists_enabled' => $multiple_on,
					'add_target_mode'        => $target_mode,
				),
				'current_wishlist_key' => $this->get_catalog_wishlist_key( $settings ),
			)
		);

		wp_enqueue_script( 'skynsmfa-wishlist' );
	}

	/**
	 * Render button on loop items.
	 *
	 * @return void
	 */
	public function render_loop_button(): void {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_loop_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'after_add_to_cart' !== ( $settings['skynsmfa_loop_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		global $product;
		if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
			return;
		}

		$this->render_button( absint( $product->get_id() ), 'loop' );
	}

	/**
	 * Render button before loop add to cart.
	 *
	 * @return void
	 */
	public function render_loop_button_before(): void {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_loop_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'before_add_to_cart' !== ( $settings['skynsmfa_loop_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		global $product;
		if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
			return;
		}

		$this->render_button( absint( $product->get_id() ), 'loop' );
	}

	/**
	 * Render button over product image.
	 *
	 * @return void
	 */
	public function render_over_image_button(): void {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_loop_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'over_image' !== ( $settings['skynsmfa_loop_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		global $product;
		if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
			return;
		}

		$this->render_button( absint( $product->get_id() ), 'over_image' );
	}

	/**
	 * Render button on single product page.
	 *
	 * @return void
	 */
	public function render_single_button_before(): void {
		if ( $this->should_use_before_cart_form_hook() ) {
			return;
		}

		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_single_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'before_add_to_cart' !== ( $settings['skynsmfa_single_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		$product = $this->get_current_product();
		if ( ! $product ) {
			return;
		}

		$this->render_button( absint( $product->get_id() ), 'single' );
	}

	public function render_single_button_after(): void {
		if ( $this->should_use_after_cart_form_hook() ) {
			return;
		}

		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_single_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'after_add_to_cart' !== ( $settings['skynsmfa_single_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		$product = $this->get_current_product();
		if ( ! $product ) {
			return;
		}

		$this->render_button( absint( $product->get_id() ), 'single' );
	}

	/**
	 * Render single-product button before the cart form (variable products).
	 *
	 * WooCommerce hides `.woocommerce-variation-add-to-cart` until a variation is chosen,
	 * which also hides hooks on before/after the add-to-cart button.
	 *
	 * @return void
	 */
	public function render_single_button_before_form(): void {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_single_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'before_add_to_cart' !== ( $settings['skynsmfa_single_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		$product = $this->get_current_product();
		if ( ! $product || ! $this->should_use_before_cart_form_hook() ) {
			return;
		}

		echo '<div class="skynsmfa-single-wishlist-wrap skynsmfa-single-wishlist-wrap--before-form">';
		$this->render_button( absint( $product->get_id() ), 'single' );
		echo '</div>';
	}

	/**
	 * Render single-product button after the cart form (variable products).
	 *
	 * WooCommerce hides `.woocommerce-variation-add-to-cart` until a variation is chosen,
	 * which also hides hooks on before/after the add-to-cart button.
	 *
	 * @return void
	 */
	public function render_single_button_after_form(): void {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_single_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'after_add_to_cart' !== ( $settings['skynsmfa_single_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		$product = $this->get_current_product();
		if ( ! $product || ! $this->should_use_after_cart_form_hook() ) {
			return;
		}

		echo '<div class="skynsmfa-single-wishlist-wrap">';
		$this->render_button( absint( $product->get_id() ), 'single' );
		echo '</div>';
	}

	/**
	 * Products whose cart form hides the add-to-cart row until options are chosen.
	 *
	 * Only variable products use outside-form placement by default (pre-1.0.3 behavior).
	 * Subscription, APOS, and other product types can opt in via skynsmfa_single_button_outside_cart_form.
	 *
	 * @param \WC_Product $product Current product.
	 * @return bool
	 */
	private function product_needs_outside_cart_form_layout( \WC_Product $product ): bool {
		if ( $product->is_type( 'variable' ) ) {
			return true;
		}

		/**
		 * Filter whether the single-product wishlist button avoids in-form placement.
		 *
		 * @param bool        $outside_form Render outside the cart form.
		 * @param \WC_Product $product      Current product.
		 */
		if ( apply_filters( 'skynsmfa_single_button_outside_cart_form', false, $product ) ) {
			return true;
		}

		/** @deprecated Use skynsmfa_single_button_outside_cart_form */
		return (bool) apply_filters( 'skynsmfa_single_button_after_cart_form', false, $product );
	}

	/**
	 * Use woocommerce_before_add_to_cart_form for the before position.
	 *
	 * @return bool
	 */
	private function should_use_before_cart_form_hook(): bool {
		$product = $this->get_current_product();

		return $product && $this->product_needs_outside_cart_form_layout( $product );
	}

	/**
	 * Use woocommerce_after_add_to_cart_form for the after position.
	 *
	 * @return bool
	 */
	private function should_use_after_cart_form_hook(): bool {
		$product = $this->get_current_product();

		return $product && $this->product_needs_outside_cart_form_layout( $product );
	}

	/**
	 * Current product in the loop or single product template.
	 *
	 * @return \WC_Product|null
	 */
	private function get_current_product(): ?\WC_Product {
		global $product;

		if ( $product instanceof \WC_Product ) {
			return $product;
		}

		return null;
	}

	/**
	 * Render button over the single product image.
	 *
	 * @return void
	 */
	public function render_single_button_over_image(): void {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return;
		}
		if ( 'yes' !== ( $settings['skynsmfa_single_icon'] ?? 'yes' ) ) {
			return;
		}
		if ( 'over_image' !== ( $settings['skynsmfa_single_position'] ?? 'after_add_to_cart' ) ) {
			return;
		}

		global $product;
		if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
			return;
		}

		$this->render_button( absint( $product->get_id() ), 'over_image' );
	}

	/**
	 * Render wishlist button.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $context Context.
	 * @return void
	 */
	private function render_button( int $product_id, string $context ): void {
		$this->ensure_session();

		$data = Session::instance()->get_data();
		$is_in_wishlist = false;
		
		if ( is_array( $data ) ) {
			foreach ( $data as $list ) {
				if ( is_array( $list ) && isset( $list[ $product_id ] ) ) {
					$is_in_wishlist = true;
					break;
				}
			}
		}

		$settings     = $this->get_settings();
		$display_mode = $this->resolve_display_mode();
		$catalog_key  = $this->get_catalog_wishlist_key( $settings );
		$prompt_add   = $this->should_prompt_list_on_add( $settings, $is_in_wishlist );

		if ( 'icon' === $display_mode ) {
			$this->render_icon_button( $product_id, $context, $is_in_wishlist, $catalog_key, $prompt_add );
		} else {
			$this->render_text_button( $product_id, $context, $is_in_wishlist, $catalog_key, $prompt_add, $display_mode );
		}
	}

	/**
	 * Normalize a display mode slug.
	 *
	 * @param string $mode Raw mode.
	 * @return string
	 */
	private function normalize_display_mode( string $mode ): string {
		return in_array( $mode, array( 'button', 'icon', 'icon_text' ), true ) ? $mode : 'button';
	}

	/**
	 * Resolve display mode for [skynsmfa_wishlist_icon].
	 *
	 * Omits global Button Customizer default: shortcode stays icon-only unless
	 * display="button", display="icon_text", or display="global" is set (backward compatible).
	 *
	 * @param string $override Optional display attribute from the shortcode.
	 * @return string
	 */
	private function resolve_shortcode_display_mode( string $override = '' ): string {
		$override = trim( $override );
		if ( '' !== $override ) {
			if ( 'global' === $override || 'customizer' === $override ) {
				return $this->resolve_display_mode();
			}

			return $this->normalize_display_mode( $override );
		}

		/**
		 * Filter the default display mode for [skynsmfa_wishlist_icon] when display is omitted.
		 *
		 * @param string               $mode     Default mode (icon preserves pre-1.0.3 behavior).
		 * @param array<string, mixed> $settings Plugin settings.
		 */
		$default = (string) apply_filters( 'skynsmfa_shortcode_default_display_mode', 'icon', $this->get_settings() );

		return $this->normalize_display_mode( $default );
	}

	/**
	 * Resolve catalog button display mode (loop, single product hooks).
	 *
	 * @return string
	 */
	private function resolve_display_mode( string $override = '' ): string {
		$override = trim( $override );
		if ( '' !== $override ) {
			return $this->normalize_display_mode( $override );
		}

		$settings = $this->get_settings();

		return $this->normalize_display_mode( (string) ( $settings['skynsmfa_display_mode'] ?? 'button' ) );
	}

	/**
	 * Render icon button.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $context Context.
	 * @param bool   $is_in_wishlist Whether product is in wishlist.
	 * @param string $wishlist_key Target list key for add/remove from catalog.
	 * @param bool   $prompt_add Whether to open list picker on add (ask mode).
	 * @param string $additional_classes Extra button classes (e.g. skynsmfa-shortcode).
	 * @return void
	 */
	private function render_icon_button( int $product_id, string $context, bool $is_in_wishlist, string $wishlist_key, bool $prompt_add, string $additional_classes = '' ): void {
		echo wp_kses(
			$this->get_icon_button_html( $product_id, $context, $is_in_wishlist, $wishlist_key, $prompt_add, $additional_classes ),
			$this->get_allowed_html()
		);
	}

	/**
	 * Build icon button HTML.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $context Context.
	 * @param bool   $is_in_wishlist Whether product is in wishlist.
	 * @param string $wishlist_key Target list key for add/remove from catalog.
	 * @param bool   $prompt_add Whether to open list picker on add (ask mode).
	 * @param string $additional_classes Extra button classes (e.g. skynsmfa-shortcode).
	 * @return string
	 */
	private function get_icon_button_html( int $product_id, string $context, bool $is_in_wishlist, string $wishlist_key, bool $prompt_add, string $additional_classes = '' ): string {
		$settings = $this->get_settings();
		$labels   = $this->get_button_labels( $settings );

		$extra_class   = ( $is_in_wishlist ? ' is-added' : '' ) . $additional_classes;
		$context_class = '';
		if ( 'over_image' === $context ) {
			$context_class = ' skynsmfa-over-image';
		}
		$context_class .= $this->get_icon_shape_class( $settings );

		$icon_html   = $this->get_icon_markup( $settings );
		$prompt_attr = $prompt_add ? ' data-skynsmfa-prompt-add="1"' : '';

		$html  = '<button type="button" class="skynsmfa-wishlist-button skynsmfa-icon-button' . esc_attr( $extra_class . $context_class ) . '" data-product-id="' . esc_attr( (string) $product_id ) . '" data-wishlist-key="' . esc_attr( $wishlist_key ) . '" data-context="' . esc_attr( $context ) . '"' . $prompt_attr . ' aria-label="' . esc_attr( $is_in_wishlist ? $labels['remove'] : $labels['add'] ) . '">';
		$html .= $icon_html;
		$html .= '</button>';

		/**
		 * Filter the wishlist icon HTML.
		 *
		 * @param string $html HTML.
		 * @param int    $product_id Product ID.
		 * @param string $context Context.
		 */
		return (string) apply_filters( 'skynsmfa_icon_html', $html, $product_id, $context );
	}

	/**
	 * Render text button.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $context Context.
	 * @param bool   $is_in_wishlist Whether product is in wishlist.
	 * @param string $wishlist_key Target list key for add/remove from catalog.
	 * @param bool   $prompt_add Whether to open list picker on add (ask mode).
	 * @param string $display_mode Button display mode (button or icon_text).
	 * @param string $additional_classes Extra button classes (e.g. skynsmfa-shortcode).
	 * @return void
	 */
	private function render_text_button( int $product_id, string $context, bool $is_in_wishlist, string $wishlist_key, bool $prompt_add, string $display_mode = 'button', string $additional_classes = '' ): void {
		echo wp_kses(
			$this->get_text_button_html( $product_id, $context, $is_in_wishlist, $wishlist_key, $prompt_add, $display_mode, $additional_classes ),
			$this->get_allowed_html()
		);
	}

	/**
	 * Build text button HTML.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $context Context.
	 * @param bool   $is_in_wishlist Whether product is in wishlist.
	 * @param string $wishlist_key Target list key for add/remove from catalog.
	 * @param bool   $prompt_add Whether to open list picker on add (ask mode).
	 * @param string $display_mode Button display mode (button or icon_text).
	 * @param string $additional_classes Extra button classes (e.g. skynsmfa-shortcode).
	 * @return string
	 */
	private function get_text_button_html( int $product_id, string $context, bool $is_in_wishlist, string $wishlist_key, bool $prompt_add, string $display_mode = 'button', string $additional_classes = '' ): string {
		$settings      = $this->get_settings();
		$labels        = $this->get_button_labels( $settings );
		$label         = $is_in_wishlist ? $labels['added'] : $labels['add'];
		$display_mode  = $this->normalize_display_mode( $display_mode );
		$extra_class   = ( $is_in_wishlist ? ' is-added' : '' ) . $additional_classes;
		$context_class = '';
		if ( 'over_image' === $context ) {
			$context_class = ' skynsmfa-over-image';
		}

		$with_icon = 'icon_text' === $display_mode;
		$icon_pos  = 'after' === (string) ( $settings['skynsmfa_icon_text_position'] ?? 'before' ) ? 'after' : 'before';
		if ( $with_icon ) {
			$context_class .= ' skynsmfa-has-icon skynsmfa-icon-pos-' . $icon_pos . $this->get_icon_shape_class( $settings );
		}
		if ( $this->uses_custom_button_style( $settings ) ) {
			$context_class .= ' skynsmfa-btn-style-custom';
			if ( sanitize_hex_color( (string) ( $settings['skynsmfa_btn_border_color'] ?? '' ) ) ) {
				$context_class .= ' skynsmfa-btn-has-border';
			}
		}

		$prompt_attr = $prompt_add ? ' data-skynsmfa-prompt-add="1"' : '';

		$inner = '';
		if ( $with_icon ) {
			$inner .= '<span class="skynsmfa-btn-text">' . esc_html( $label ) . '</span>';
			$icon_span = '<span class="skynsmfa-btn-icon" aria-hidden="true">' . $this->get_icon_markup( $settings ) . '</span>';
			$inner     = ( 'after' === $icon_pos ) ? $inner . $icon_span : $icon_span . $inner;
		} else {
			$inner = esc_html( $label );
		}

		$aria_label = $is_in_wishlist ? $labels['remove'] : $labels['add'];

		$html  = '<button type="button" class="button skynsmfa-wishlist-button' . esc_attr( $extra_class . $context_class ) . '" data-product-id="' . esc_attr( (string) $product_id ) . '" data-wishlist-key="' . esc_attr( $wishlist_key ) . '" data-context="' . esc_attr( $context ) . '"' . $prompt_attr . ' aria-label="' . esc_attr( $aria_label ) . '">';
		$html .= $inner;
		$html .= '</button>';

		/**
		 * Filter the wishlist button HTML.
		 *
		 * @param string $html HTML.
		 * @param int    $product_id Product ID.
		 * @param string $context Context.
		 */
		return (string) apply_filters( 'skynsmfa_icon_html', $html, $product_id, $context );
	}

	/**
	 * Render the [skynsmfa_wishlist_icon] shortcode.
	 *
	 * Attributes: product_id, display (optional icon|icon_text|button|global; defaults to icon-only).
	 *
	 * @param array $atts Shortcode attributes.
	 * @return string
	 */
	public function render_icon_shortcode( array $atts = array() ): string {
		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return '';
		}
		if ( 'yes' !== ( $settings['skynsmfa_custom_shortcode'] ?? 'yes' ) ) {
			return '';
		}

		$atts = shortcode_atts(
			array(
				'product_id' => 0,
				'display'    => '',
			),
			$atts,
			'skynsmfa_wishlist_icon'
		);

		$product_id = absint( $atts['product_id'] );
		if ( $product_id <= 0 ) {
			$product_id = $this->resolve_shortcode_product_id();
		}
		if ( $product_id <= 0 ) {
			return '';
		}

		$this->ensure_session();

		$data = Session::instance()->get_data();
		$is_in_wishlist = false;

		if ( is_array( $data ) ) {
			foreach ( $data as $list ) {
				if ( is_array( $list ) && isset( $list[ $product_id ] ) ) {
					$is_in_wishlist = true;
					break;
				}
			}
		}

		$settings      = $this->get_settings();
		$display_mode  = $this->resolve_shortcode_display_mode( (string) $atts['display'] );
		$catalog_key   = $this->get_catalog_wishlist_key( $settings );
		$prompt_add    = $this->should_prompt_list_on_add( $settings, $is_in_wishlist );
		$extra_classes = ' skynsmfa-shortcode';

		if ( 'icon' === $display_mode ) {
			$html = $this->get_icon_button_html( $product_id, 'shortcode', $is_in_wishlist, $catalog_key, $prompt_add, $extra_classes );
		} else {
			$html = $this->get_text_button_html( $product_id, 'shortcode', $is_in_wishlist, $catalog_key, $prompt_add, $display_mode, $extra_classes );
		}

		return wp_kses( $html, $this->get_allowed_html() );
	}

	/**
	 * Resolve product ID for [skynsmfa_wishlist_icon] when attribute is omitted.
	 *
	 * Uses the current WooCommerce product in the loop, else the single product page.
	 *
	 * @return int
	 */
	private function resolve_shortcode_product_id(): int {
		global $product;

		if ( $product && is_a( $product, 'WC_Product' ) ) {
			return absint( $product->get_id() );
		}

		if ( is_singular( 'product' ) ) {
			return absint( get_queried_object_id() );
		}

		$post_id = get_the_ID();
		if ( $post_id && 'product' === get_post_type( $post_id ) ) {
			return absint( $post_id );
		}

		return 0;
	}

	/**
	 * Render the [skynsmfa_wishlist_menu_icon] shortcode.
	 *
	 * @param array<string, string>|string $atts Shortcode attributes.
	 * @return string
	 */
	public function render_menu_icon_shortcode( $atts = array() ): string {
		$atts = shortcode_atts(
			array(
				'count_visibility' => 'greater_than_zero',
				'icon_size'        => '28',
				'icon_color'       => '',
				'badge_color'      => '',
				'badge_text_color' => '',
				'custom_icon_id'   => '0',
			),
			is_array( $atts ) ? $atts : array(),
			'skynsmfa_wishlist_menu_icon'
		);

		return $this->render_menu_icon(
			array(
				'countVisibility' => (string) $atts['count_visibility'],
				'iconSize'        => absint( $atts['icon_size'] ),
				'iconColor'       => (string) $atts['icon_color'],
				'badgeColor'      => (string) $atts['badge_color'],
				'badgeTextColor'  => (string) $atts['badge_text_color'],
				'customIconId'    => absint( $atts['custom_icon_id'] ),
			)
		);
	}

	/**
	 * Normalize menu icon block / shortcode attributes.
	 *
	 * @param array<string, mixed> $attributes Raw attributes.
	 * @return array<string, mixed>
	 */
	private function normalize_menu_icon_attributes( array $attributes ): array {
		$count_visibility = isset( $attributes['countVisibility'] )
			? sanitize_text_field( (string) $attributes['countVisibility'] )
			: 'greater_than_zero';

		if ( ! in_array( $count_visibility, array( 'greater_than_zero', 'always', 'never' ), true ) ) {
			$count_visibility = 'greater_than_zero';
		}

		$icon_size = isset( $attributes['iconSize'] ) ? absint( $attributes['iconSize'] ) : 28;
		$icon_size = max( 16, min( 80, $icon_size ) );

		$icon_color = isset( $attributes['iconColor'] ) ? sanitize_hex_color( (string) $attributes['iconColor'] ) : '';
		$badge_color = isset( $attributes['badgeColor'] ) ? sanitize_hex_color( (string) $attributes['badgeColor'] ) : '';
		$badge_text_color = isset( $attributes['badgeTextColor'] ) ? sanitize_hex_color( (string) $attributes['badgeTextColor'] ) : '';

		$custom_icon_id  = isset( $attributes['customIconId'] ) ? absint( $attributes['customIconId'] ) : 0;
		$custom_icon_url = isset( $attributes['customIconUrl'] ) ? esc_url_raw( (string) $attributes['customIconUrl'] ) : '';

		if ( $custom_icon_id > 0 ) {
			$attachment_url = wp_get_attachment_image_url( $custom_icon_id, 'full' );
			if ( is_string( $attachment_url ) && '' !== $attachment_url ) {
				$custom_icon_url = $attachment_url;
			}
		}

		return array(
			'count_visibility'  => $count_visibility,
			'icon_size'         => $icon_size,
			'icon_color'        => is_string( $icon_color ) ? $icon_color : '',
			'badge_color'       => is_string( $badge_color ) ? $badge_color : '',
			'badge_text_color'  => is_string( $badge_text_color ) ? $badge_text_color : '',
			'custom_icon_url'   => $custom_icon_url,
		);
	}

	/**
	 * Render header/menu favorites icon with counter (block + shortcode).
	 *
	 * @param array<string, mixed> $attributes Block or shortcode attributes.
	 * @return string
	 */
	public function render_menu_icon( array $attributes = array() ): string {
		if ( ! Plugin::is_wishlist_enabled() ) {
			return '';
		}

		$settings = $this->get_settings();
		if ( 'yes' !== ( $settings['skynsmfa_menu_icon'] ?? 'yes' ) ) {
			return '';
		}

		$normalized = $this->normalize_menu_icon_attributes( $attributes );

		$this->ensure_session();

		$count = Session::instance()->get_item_count();
		$count = (int) apply_filters( 'skynsmfa_wishlist_item_count', $count );

		$url = $this->get_wishlist_page_url();
		$url = (string) apply_filters( 'skynsmfa_wishlist_menu_icon_url', $url );

		$skynsmfa_url              = $url;
		$skynsmfa_count            = $count;
		$skynsmfa_count_visibility = $normalized['count_visibility'];
		$skynsmfa_icon_size        = $normalized['icon_size'];
		$skynsmfa_icon_color       = $normalized['icon_color'];
		$skynsmfa_badge_color      = $normalized['badge_color'];
		$skynsmfa_badge_text_color = $normalized['badge_text_color'];
		$skynsmfa_custom_icon_url  = $normalized['custom_icon_url'];
		$skynsmfa_icon_style       = $this->get_icon_style( $settings );
		$skynsmfa_aria_label       = sprintf(
			/* translators: %d: number of saved favorites */
			_n( 'Favorites, %d item', 'Favorites, %d items', $count, 'skynet-smart-favorites' ),
			$count
		);

		ob_start();
		include SKYNSMFA_PATH . 'templates/menu-icon.php';
		$html = ob_get_clean();

		if ( ! is_string( $html ) ) {
			return '';
		}

		return wp_kses( $html, $this->get_menu_icon_allowed_html() );
	}

	/**
	 * URL for the assigned wishlist page.
	 *
	 * @return string
	 */
	public function get_wishlist_page_url(): string {
		$settings = $this->get_settings();
		$page_id  = absint( $settings['wishlist_page_id'] ?? 0 );

		if ( $page_id > 0 && 'publish' === get_post_status( $page_id ) ) {
			return (string) get_permalink( $page_id );
		}

		return home_url( '/' );
	}

	/**
	 * Render the [skynsmfa_wishlist] shortcode.
	 *
	 * @return string
	 */
	public function render_shortcode(): string {
		$this->ensure_session();

		$settings = $this->get_settings();
		if ( ! Plugin::is_wishlist_enabled() ) {
			return '';
		}

		if ( ! function_exists( 'WC' ) ) {
			return '<p>' . esc_html__( 'WooCommerce is required for wishlist.', 'skynet-smart-favorites' ) . '</p>';
		}

		$data             = Session::instance()->get_data();
		$multiple_enabled = 'yes' === ( $settings['skynsmfa_multiple_enable'] ?? 'yes' );

		if ( ! $multiple_enabled ) {
			$data = array(
				'default' => isset( $data['default'] ) && is_array( $data['default'] ) ? $data['default'] : array(),
			);
		}

		$skynsmfa_grid_columns = $this->get_wishlist_grid_columns();
		$skynsmfa_bulk_enabled   = Feature_Gate::can_bulk_cart();

		$layout = isset( $settings['skynsmfa_page_layout'] ) ? (string) $settings['skynsmfa_page_layout'] : 'table';
		$settings['skynsmfa_page_layout'] = Feature_Gate::resolve_page_layout( $layout );

		ob_start();
		include SKYNSMFA_PATH . 'templates/wishlist-page.php';
		return ob_get_clean();
	}

	/**
	 * Grid column count for the wishlist page (matches WooCommerce shop catalog when possible).
	 *
	 * @return int
	 */
	private function get_wishlist_grid_columns(): int {
		$columns = 4;

		if ( function_exists( 'wc_get_default_products_per_row' ) ) {
			$columns = wc_get_default_products_per_row();
		} else {
			$columns = absint( get_option( 'woocommerce_catalog_columns', 4 ) );
		}

		$columns = max( 1, min( 6, absint( $columns ) ) );

		/**
		 * Filter wishlist grid column count on the frontend.
		 *
		 * @param int $columns Column count (1–6).
		 */
		return (int) apply_filters( 'skynsmfa_wishlist_grid_columns', $columns );
	}

	/**
	 * Cache-busting version string for a plugin asset file.
	 *
	 * @param string $relative_path Path relative to plugin root.
	 * @return string
	 */
	private function get_asset_version( string $relative_path ): string {
		$path = SKYNSMFA_PATH . ltrim( $relative_path, '/' );
		if ( ! is_readable( $path ) ) {
			return SKYNSMFA_VERSION;
		}

		$mtime = filemtime( $path );
		if ( false === $mtime ) {
			return SKYNSMFA_VERSION;
		}

		return SKYNSMFA_VERSION . '.' . (string) $mtime;
	}

	/**
	 * Sorted wishlist keys for the list-picker modal (default first).
	 *
	 * @param array<string, mixed> $data Session wishlist data.
	 * @return list<string>
	 */
	private function get_picker_list_keys( array $data ): array {
		$keys = array_keys( $data );
		$keys = array_values(
			array_filter(
				$keys,
				static function ( $key ): bool {
					return is_string( $key ) && '' !== $key;
				}
			)
		);

		if ( ! in_array( 'default', $keys, true ) ) {
			array_unshift( $keys, 'default' );
		}

		usort(
			$keys,
			static function ( string $a, string $b ): int {
				if ( 'default' === $a ) {
					return -1;
				}
				if ( 'default' === $b ) {
					return 1;
				}
				return strcmp( $a, $b );
			}
		);

		if ( empty( $keys ) ) {
			return array( 'default' );
		}

		return $keys;
	}

	/**
	 * Read saved settings.
	 *
	 * @return array
	 */
	private function get_settings(): array {
		$defaults = array(
			'skynsmfa_enable'               => 'yes',
			'skynsmfa_multiple_enable'      => 'yes',
			'skynsmfa_add_target_mode'      => 'last_used',
			'skynsmfa_loop_icon'            => 'yes',
			'skynsmfa_single_icon'          => 'yes',
			'skynsmfa_loop_position'        => 'after_add_to_cart',
			'skynsmfa_single_position'      => 'after_add_to_cart',
			'skynsmfa_display_mode'         => 'button',
			'skynsmfa_custom_shortcode'     => 'yes',
			'skynsmfa_menu_icon'            => 'yes',
			'skynsmfa_notification_type'    => 'toast',
			'skynsmfa_page_layout'          => 'table',
			'skynsmfa_rate_limit_per_min'   => 120,
			'wishlist_page_id'              => 0,
			'skynsmfa_icon_style'           => 'heart',
			'skynsmfa_icon_custom_id'       => 0,
			'skynsmfa_icon_text_position'   => 'before',
			'skynsmfa_icon_fill'            => 'outline',
			'skynsmfa_icon_color'           => '',
			'skynsmfa_icon_active_color'    => '',
			'skynsmfa_icon_hover_color'     => '',
			'skynsmfa_icon_size'            => 24,
			'skynsmfa_icon_shape'           => 'none',
			'skynsmfa_btn_bg_color'         => '',
			'skynsmfa_btn_text_color'       => '',
			'skynsmfa_btn_border_color'     => '',
			'skynsmfa_btn_active_bg_color'  => '',
			'skynsmfa_btn_active_text_color' => '',
			'skynsmfa_btn_radius'           => '',
			'skynsmfa_label_add'            => '',
			'skynsmfa_label_added'          => '',
			'skynsmfa_label_remove'         => '',
			'skynsmfa_custom_css'           => '',
		);

		$stored = get_option( 'skynsmfa_settings', array() );
		if ( ! is_array( $stored ) ) {
			$stored = array();
		}

		$settings = array_merge( $defaults, $stored );

		if ( 'below_gallery' === ( $settings['skynsmfa_single_position'] ?? '' ) ) {
			$settings['skynsmfa_single_position'] = 'after_add_to_cart';
		}

		return $settings;
	}

	/**
	 * Resolve custom add/added/remove labels (fall back to defaults).
	 *
	 * @param array $settings Plugin settings.
	 * @return array{add:string,added:string,remove:string}
	 */
	private function get_button_labels( array $settings ): array {
		$add    = trim( (string) ( $settings['skynsmfa_label_add'] ?? '' ) );
		$added  = trim( (string) ( $settings['skynsmfa_label_added'] ?? '' ) );
		$remove = trim( (string) ( $settings['skynsmfa_label_remove'] ?? '' ) );

		return array(
			'add'    => '' !== $add ? $add : __( 'Add to wishlist', 'skynet-smart-favorites' ),
			'added'  => '' !== $added ? $added : __( 'Already in wishlist', 'skynet-smart-favorites' ),
			'remove' => '' !== $remove ? $remove : __( 'Remove from wishlist', 'skynet-smart-favorites' ),
		);
	}

	/**
	 * Configured icon style (heart|star|bookmark).
	 *
	 * @param array $settings Plugin settings.
	 * @return string
	 */
	private function get_icon_style( array $settings ): string {
		$style = (string) ( $settings['skynsmfa_icon_style'] ?? 'heart' );

		return in_array( $style, self::get_icon_style_keys(), true ) ? $style : 'heart';
	}

	/**
	 * Selectable built-in icon style keys.
	 *
	 * @return list<string>
	 */
	public static function get_icon_style_keys(): array {
		return array( 'heart', 'star', 'bookmark', 'bag', 'tag', 'gift', 'thumb', 'plus' );
	}

	/**
	 * Markup for the favorites icon: a custom uploaded image when set, else the built-in SVG.
	 *
	 * @param array $settings Plugin settings.
	 * @return string
	 */
	private function get_icon_markup( array $settings ): string {
		$custom_id = absint( $settings['skynsmfa_icon_custom_id'] ?? 0 );
		if ( $custom_id > 0 ) {
			$url = wp_get_attachment_image_url( $custom_id, 'full' );
			if ( is_string( $url ) && '' !== $url ) {
				$size = max( 16, min( 64, absint( $settings['skynsmfa_icon_size'] ?? 24 ) ) );

				return '<img class="skynsmfa-wishlist-icon skynsmfa-wishlist-icon--custom" src="' . esc_url( $url ) . '" alt="" width="' . esc_attr( (string) $size ) . '" height="' . esc_attr( (string) $size ) . '" loading="lazy" decoding="async" />';
			}
		}

		$skynsmfa_icon_style = $this->get_icon_style( $settings );

		ob_start();
		include SKYNSMFA_PATH . 'templates/icon.php';

		return (string) ob_get_clean();
	}

	/**
	 * Icon background shape + fill classes (leading space) for icon buttons.
	 *
	 * @param array $settings Plugin settings.
	 * @return string
	 */
	private function get_icon_shape_class( array $settings ): string {
		$classes = '';

		$shape = (string) ( $settings['skynsmfa_icon_shape'] ?? 'none' );
		if ( in_array( $shape, array( 'rounded', 'circle' ), true ) ) {
			$classes .= ' skynsmfa-icon-shape-' . $shape;
		}

		if ( 'filled' === (string) ( $settings['skynsmfa_icon_fill'] ?? 'outline' ) ) {
			$classes .= ' skynsmfa-icon-fill-filled';
		}

		return $classes;
	}

	/**
	 * Whether text-button custom colors from Button Customizer should apply.
	 *
	 * @param array $settings Plugin settings.
	 * @return bool
	 */
	private function uses_custom_button_style( array $settings ): bool {
		return $this->has_saved_button_colors( $settings );
	}

	/**
	 * Whether saved settings include any text-button custom color or radius.
	 *
	 * @param array $settings Plugin settings.
	 * @return bool
	 */
	private function has_saved_button_colors( array $settings ): bool {
		foreach ( array(
			'skynsmfa_btn_bg_color',
			'skynsmfa_btn_text_color',
			'skynsmfa_btn_border_color',
			'skynsmfa_btn_active_bg_color',
			'skynsmfa_btn_active_text_color',
		) as $key ) {
			if ( sanitize_hex_color( (string) ( $settings[ $key ] ?? '' ) ) ) {
				return true;
			}
		}

		$radius = trim( (string) ( $settings['skynsmfa_btn_radius'] ?? '' ) );

		return '' !== $radius && is_numeric( $radius );
	}

	/**
	 * Whether any Button Customizer value was saved (non-default appearance).
	 *
	 * @param array $settings Plugin settings.
	 * @return bool
	 */
	private function has_appearance_customization( array $settings ): bool {
		if ( '' !== trim( (string) ( $settings['skynsmfa_custom_css'] ?? '' ) ) ) {
			return true;
		}

		foreach ( array(
			'skynsmfa_icon_color',
			'skynsmfa_icon_active_color',
			'skynsmfa_icon_hover_color',
		) as $key ) {
			if ( sanitize_hex_color( (string) ( $settings[ $key ] ?? '' ) ) ) {
				return true;
			}
		}

		if ( $this->has_saved_button_colors( $settings ) ) {
			return true;
		}

		if ( 24 !== max( 16, min( 64, absint( $settings['skynsmfa_icon_size'] ?? 24 ) ) ) ) {
			return true;
		}

		if ( in_array( (string) ( $settings['skynsmfa_icon_shape'] ?? 'none' ), array( 'rounded', 'circle' ), true ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Build inline CSS (custom properties) from the appearance settings.
	 *
	 * Only non-empty colors are written so the theme default still wins when unset.
	 * Returns empty string when no customization was saved (storefront unchanged).
	 *
	 * @param array $settings Plugin settings.
	 * @return string
	 */
	private function build_appearance_css( array $settings ): string {
		if ( ! $this->has_appearance_customization( $settings ) ) {
			return '';
		}

		$display_mode = (string) ( $settings['skynsmfa_display_mode'] ?? 'button' );
		$icon_color   = sanitize_hex_color( (string) ( $settings['skynsmfa_icon_color'] ?? '' ) );
		$active_color = sanitize_hex_color( (string) ( $settings['skynsmfa_icon_active_color'] ?? '' ) );
		$hover_color  = sanitize_hex_color( (string) ( $settings['skynsmfa_icon_hover_color'] ?? '' ) );

		$btn_custom      = $this->uses_custom_button_style( $settings );
		$btn_bg          = $btn_custom ? sanitize_hex_color( (string) ( $settings['skynsmfa_btn_bg_color'] ?? '' ) ) : '';
		$btn_text        = $btn_custom ? sanitize_hex_color( (string) ( $settings['skynsmfa_btn_text_color'] ?? '' ) ) : '';
		$btn_border      = $btn_custom ? sanitize_hex_color( (string) ( $settings['skynsmfa_btn_border_color'] ?? '' ) ) : '';
		$btn_active_bg   = $btn_custom ? sanitize_hex_color( (string) ( $settings['skynsmfa_btn_active_bg_color'] ?? '' ) ) : '';
		$btn_active_text = $btn_custom ? sanitize_hex_color( (string) ( $settings['skynsmfa_btn_active_text_color'] ?? '' ) ) : '';

		$size  = max( 16, min( 64, absint( $settings['skynsmfa_icon_size'] ?? 24 ) ) );
		$shape = (string) ( $settings['skynsmfa_icon_shape'] ?? 'none' );

		$shared_vars = array();
		$shared_vars[] = '--skynsmfa-icon-size:' . $size . 'px';
		if ( $icon_color ) {
			$shared_vars[] = '--skynsmfa-icon-color:' . $icon_color;
		}
		if ( $active_color ) {
			$shared_vars[] = '--skynsmfa-icon-active-color:' . $active_color;
		}
		if ( $hover_color ) {
			$shared_vars[] = '--skynsmfa-icon-hover-color:' . $hover_color;
		}
		if ( $btn_bg ) {
			$shared_vars[] = '--skynsmfa-btn-bg:' . $btn_bg;
		}
		if ( $btn_text ) {
			$shared_vars[] = '--skynsmfa-btn-text:' . $btn_text;
		}
		if ( $btn_border ) {
			$shared_vars[] = '--skynsmfa-btn-border:' . $btn_border;
		}
		if ( $btn_active_bg ) {
			$shared_vars[] = '--skynsmfa-btn-active-bg:' . $btn_active_bg;
		}
		if ( $btn_active_text ) {
			$shared_vars[] = '--skynsmfa-btn-active-text:' . $btn_active_text;
		}
		if ( 'rounded' === $shape ) {
			$shared_vars[] = '--skynsmfa-icon-shape-radius:6px';
		} elseif ( 'circle' === $shape ) {
			$shared_vars[] = '--skynsmfa-icon-shape-radius:50%';
		}

		$css_blocks = array();
		$css_blocks[] = '.skynsmfa-wishlist-button{' . implode( ';', $shared_vars ) . '}';

		if ( in_array( $display_mode, array( 'button', 'icon_text' ), true ) ) {
			$btn_radius = trim( (string) ( $settings['skynsmfa_btn_radius'] ?? '' ) );
			if ( $btn_custom && '' !== $btn_radius && is_numeric( $btn_radius ) ) {
				$css_blocks[] = '.skynsmfa-wishlist-button.button:not(.skynsmfa-icon-button){--skynsmfa-btn-radius:' . max( 0, min( 40, absint( $btn_radius ) ) ) . 'px}';
			}
		}

		$custom_css = trim( (string) ( $settings['skynsmfa_custom_css'] ?? '' ) );
		if ( '' !== $custom_css ) {
			// Re-validate stored CSS before output so tampered DB values cannot bypass save-time checks.
			$validated = Css_Sanitizer::prepare_for_save( $custom_css );
			if ( $validated['success'] && '' !== $validated['css'] ) {
				$css_blocks[] = Css_Sanitizer::minify( $validated['css'] );
			}
		}

		return implode( '', $css_blocks );
	}

	/**
	 * Wishlist key used on catalog buttons (shop, single, shortcode) when not prompting.
	 *
	 * @param array $settings Plugin settings.
	 * @return string
	 */
	private function get_catalog_wishlist_key( array $settings ): string {
		if ( 'yes' !== ( $settings['skynsmfa_multiple_enable'] ?? 'yes' ) ) {
			return 'default';
		}

		$mode = (string) ( $settings['skynsmfa_add_target_mode'] ?? 'last_used' );
		if ( ! in_array( $mode, array( 'default', 'last_used', 'ask' ), true ) ) {
			$mode = 'last_used';
		}

		if ( 'default' === $mode ) {
			return 'default';
		}

		return Session::instance()->get_last_wishlist_key();
	}

	/**
	 * Whether catalog add should open the list picker (ask mode).
	 *
	 * @param array $settings Plugin settings.
	 * @param bool  $is_in_wishlist Product already in some list.
	 * @return bool
	 */
	private function should_prompt_list_on_add( array $settings, bool $is_in_wishlist ): bool {
		if ( $is_in_wishlist ) {
			return false;
		}
		if ( 'yes' !== ( $settings['skynsmfa_multiple_enable'] ?? 'yes' ) ) {
			return false;
		}
		return 'ask' === (string) ( $settings['skynsmfa_add_target_mode'] ?? 'last_used' );
	}

	/**
	 * Get allowed HTML for icon and buttons.
	 *
	 * @return array
	 */
	private function get_allowed_html(): array {
		return array_merge(
			wp_kses_allowed_html( 'post' ),
			array(
				'svg' => array(
					'class'       => true,
					'aria-hidden' => true,
					'focusable'   => true,
					'viewbox'     => true,
					'viewBox'     => true,
					'xmlns'       => true,
				),
				'path' => array(
					'd'    => true,
					'fill' => true,
				),
				'img' => array(
					'class'    => true,
					'src'      => true,
					'alt'      => true,
					'width'    => true,
					'height'   => true,
					'loading'  => true,
					'decoding' => true,
				),
				'span' => array(
					'class'       => true,
					'aria-hidden' => true,
				),
				'button' => array(
					'type'                    => true,
					'class'                   => true,
					'data-product-id'         => true,
					'data-variation-id'       => true,
					'data-wishlist-key'       => true,
					'data-context'            => true,
					'data-skynsmfa-prompt-add' => true,
					'aria-label'              => true,
				),
			)
		);
	}

	/**
	 * Allowed HTML for the menu icon link.
	 *
	 * @return array<string, array<string, bool>>
	 */
	private function get_menu_icon_allowed_html(): array {
		return array(
			'a' => array(
				'href'                  => true,
				'class'                 => true,
				'style'                 => true,
				'aria-label'            => true,
				'data-count-visibility' => true,
			),
			'span' => array(
				'class'  => true,
				'hidden' => true,
			),
			'img' => array(
				'class'    => true,
				'src'      => true,
				'alt'      => true,
				'width'    => true,
				'height'   => true,
				'loading'  => true,
				'decoding' => true,
			),
			'svg' => array(
				'class'       => true,
				'aria-hidden' => true,
				'focusable'   => true,
				'viewbox'     => true,
				'viewBox'     => true,
				'xmlns'       => true,
			),
			'path' => array(
				'd'    => true,
				'fill' => true,
			),
		);
	}
}

