<?php

namespace Nelio_AB_Testing\WooCommerce\Experiment_Library\Product_Experiment;

defined( 'ABSPATH' ) || exit;

use Nelio_AB_Testing\Experiment_Library\Post_Experiment\Control_Comments_Loader;

use function add_action;
use function add_filter;
use function get_permalink;
use function wc_get_product;
use function Nelio_AB_Testing\WooCommerce\Compat\get_product_id;
use function Nelio_AB_Testing\WooCommerce\Helpers\Actions\notify_alternative_loaded;

// We need a “mid” priority to be able to load Elementor alternative content.
// But it can’t be “high” because, if it is, then test scope can’t be properly evaluated.
add_filter(
	'nab_nab/wc-product_experiment_priority',
	fn() => 'mid'
);

add_filter(
	'nab_is_nab/wc-product_relevant_in_ajax_request',
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended
	fn( $r ) => $r || isset( $_REQUEST['wc-ajax'] )
);

add_filter(
	'nab_is_nab/wc-product_relevant_in_rest_request',
	'__return_true'
);

/**
 * Callback to get alternative loaders.
 *
 * @param TWC_Product_Alternative_Attributes|TWC_Product_Control_Attributes $alternative   Alternative.
 * @param TWC_Product_Control_Attributes                                    $control       Control.
 * @param int                                                               $experiment_id Experiment ID.
 * @param string                                                            $alternative_id Alternative ID.
 *
 * @return void
 */
function load_alternative( $alternative, $control, $experiment_id, $alternative_id ) {

	add_filter(
		'nab_enable_custom_woocommerce_hooks',
		function ( $enabled, $product_id ) use ( $control ) {
			return $enabled || $product_id === $control['postId'];
		},
		10,
		2
	);

	add_filter(
		'nab_woocommerce_is_price_testing_enabled',
		function ( $enabled, $product_id ) use ( $control ) {
			if ( $product_id !== $control['postId'] ) {
				return $enabled;
			}
			return empty( $control['disablePriceTesting'] );
		},
		10,
		2
	);

	add_action(
		'wp',
		function () use ( $control, $alternative, $experiment_id ) {
			if ( ! is_singular( 'product' ) ) {
				return;
			}
			$current_id     = get_the_ID();
			$control_id     = absint( $control['postId'] );
			$alternative_id = absint( $alternative['postId'] );
			if ( $current_id === $control_id || $current_id === $alternative_id ) {
				notify_alternative_loaded( $experiment_id );
			}
		}
	);

	$alt_product = get_alt_product( $alternative, $control['postId'], $experiment_id );
	if ( $alt_product->is_proper_woocommerce_product() ) {
		add_hooks_to_switch_products( $alt_product, $alternative, $control, $experiment_id, $alternative_id );

		/**
		 * Runs when loading an alternative WooCommerce product.
		 *
		 * @param \Nelio_AB_Testing\WooCommerce\Experiment_Library\Product_Experiment\IRunning_Alternative_Product $alt_product The product.
		 * @param number $experiment_id The experiment ID.
		 *
		 * @since 7.4.5
		 */
		do_action( 'nab_load_proper_alternative_woocommerce_product', $alt_product, $experiment_id );
	}

	nab_add_filter(
		'woocommerce_product_name',
		function ( $name, $product_id ) use ( &$alt_product ) {
			if ( $product_id !== $alt_product->get_control_id() ) {
				return $name;
			}

			notify_alternative_loaded( $alt_product->get_experiment_id() );
			if ( $alt_product->should_use_control_value() ) {
				return $name;
			}

			return $alt_product->get_name();
		},
		1,
		2
	);

	if ( $alt_product->is_description_supported() ) {
		nab_add_filter(
			'woocommerce_product_description',
			function ( $description, $product_id ) use ( &$alt_product ) {
				if ( $product_id !== $alt_product->get_control_id() ) {
					return $description;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $description;
				}

				return $alt_product->get_description();
			},
			1,
			2
		);
	}

	nab_add_filter(
		'woocommerce_product_short_description',
		function ( $short_description, $product_id ) use ( &$alt_product ) {
			if ( $product_id !== $alt_product->get_control_id() ) {
				return $short_description;
			}

			notify_alternative_loaded( $alt_product->get_experiment_id() );
			if ( $alt_product->should_use_control_value() ) {
				return $short_description;
			}

			return $alt_product->get_short_description();
		},
		1,
		2
	);

	nab_add_filter(
		'woocommerce_product_image_id',
		function ( $image_id, $product_id ) use ( &$alt_product ) {
			if ( $product_id !== $alt_product->get_control_id() ) {
				return $image_id;
			}

			notify_alternative_loaded( $alt_product->get_experiment_id() );
			if ( $alt_product->should_use_control_value() ) {
				return $image_id;
			}

			return $alt_product->get_image_id();
		},
		1,
		2
	);

	if ( $alt_product->is_gallery_supported() ) {
		nab_add_filter(
			'woocommerce_product_gallery_ids',
			function ( $image_ids, $product_id ) use ( &$alt_product ) {
				if ( $product_id !== $alt_product->get_control_id() ) {
					return $image_ids;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $image_ids;
				}

				return $alt_product->get_gallery_image_ids();
			},
			1,
			2
		);
	}

	if ( ! $alt_product->has_variation_data() ) {

		nab_add_filter(
			'woocommerce_product_regular_price',
			function ( $price, $product_id ) use ( &$alt_product ) {
				if ( $product_id !== $alt_product->get_control_id() ) {
					return $price;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $price;
				}

				$regular_price = $alt_product->get_regular_price();
				return empty( $regular_price ) ? $price : $regular_price;
			},
			1,
			2
		);

		if ( $alt_product->is_sale_price_supported() ) {
			nab_add_filter(
				'woocommerce_product_sale_price',
				function ( $price, $product_id, $regular_price ) use ( &$alt_product ) {
					if ( $product_id !== $alt_product->get_control_id() ) {
						return $price;
					}

					notify_alternative_loaded( $alt_product->get_experiment_id() );
					if ( $alt_product->should_use_control_value() ) {
						return $price;
					}

					$sale_price = $alt_product->get_sale_price();
					return empty( $sale_price ) ? $regular_price : $sale_price;
				},
				1,
				3
			);
		}
	} else {

		nab_add_filter(
			'woocommerce_variation_description',
			function ( $short_description, $product_id, $variation_id ) use ( &$alt_product ) {
				/** @var int $variation_id */

				if ( $product_id !== $alt_product->get_control_id() ) {
					return $short_description;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $short_description;
				}

				return $alt_product->get_variation_field( $variation_id, 'description', '' );
			},
			1,
			3
		);

		nab_add_filter(
			'woocommerce_variation_image_id',
			function ( $image_id, $product_id, $variation_id ) use ( &$alt_product ) {
				/** @var int $variation_id */

				if ( $product_id !== $alt_product->get_control_id() ) {
					return $image_id;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $image_id;
				}

				return $alt_product->get_variation_field( $variation_id, 'imageId', 0 );
			},
			1,
			3
		);

		nab_add_filter(
			'woocommerce_variation_regular_price',
			function ( $price, $product_id, $variation_id ) use ( &$alt_product ) {
				/** @var int $variation_id */

				if ( $product_id !== $alt_product->get_control_id() ) {
					return $price;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $price;
				}

				return $alt_product->get_variation_field( $variation_id, 'regularPrice', $price );
			},
			1,
			3
		);

		nab_add_filter(
			'woocommerce_variation_sale_price',
			function ( $price, $product_id, $regular_price, $variation_id ) use ( &$alt_product ) {
				/** @var int $variation_id */

				if ( $product_id !== $alt_product->get_control_id() ) {
					return $price;
				}

				notify_alternative_loaded( $alt_product->get_experiment_id() );
				if ( $alt_product->should_use_control_value() ) {
					return $price;
				}

				return $alt_product->get_variation_field( $variation_id, 'salePrice', $regular_price );
			},
			1,
			4
		);

	}
}
add_action( 'nab_nab/wc-product_load_alternative', __NAMESPACE__ . '\load_alternative', 10, 4 );

/**
 * Adds hooks to switch products.
 *
 * @param IRunning_Alternative_Product                                      $alt_product    Alt product.
 * @param TWC_Product_Alternative_Attributes|TWC_Product_Control_Attributes $alt_attrs      Alternative.
 * @param TWC_Product_Control_Attributes                                    $control_attrs  Control.
 * @param int                                                               $experiment_id  Experiment ID.
 * @param string                                                            $alternative_id Alternative ID.
 *
 * @return void
 */
function add_hooks_to_switch_products( $alt_product, $alt_attrs, $control_attrs, $experiment_id, $alternative_id ) {
	add_filter(
		'posts_results',
		function ( $posts ) use ( &$alt_product ) {
			/** @var list<\WP_Post> $posts */

			if ( ! is_singular() || ! is_main_query() ) {
				return $posts;
			}

			return array_map(
				function ( $post ) use ( &$alt_product ) {
					if ( $post->ID !== $alt_product->get_control_id() ) {
						return $post;
					}

					$alternative_post = get_post( $alt_product->get_id() );
					if ( empty( $alternative_post ) ) {
						return $post;
					}

					$post              = $alternative_post;
					$post->post_status = 'publish';
					/** @var \WP_Query|null $wp_query */
					global $wp_query;
					if ( ! empty( $wp_query ) && $wp_query->queried_object_id === $alt_product->get_control_id() ) {
						$wp_query->queried_object    = $post;
						$wp_query->queried_object_id = $post->ID;
					}
					return $post;
				},
				$posts
			);
		}
	);

	// Use control type instead of our own nab-alt-product.
	add_filter(
		'woocommerce_product_type_query',
		function ( $type, $product_id ) use ( &$alt_product ) {
			if ( $alt_product->get_id() !== $product_id ) {
				return $type;
			}
			$control = $alt_product->get_control();
			return ! empty( $control ) ? $control->get_type() : $type;
		},
		10,
		2
	);

	// Simulate product is publish.
	add_filter(
		'woocommerce_product_get_status',
		function ( $status, $product ) use ( &$alt_product ) {
			/** @var string      $status  */
			/** @var \WC_Product $product */

			return $product->get_id() === $alt_product->get_id() ? 'publish' : $status;
		},
		10,
		2
	);

	// Retrieve control children (e.g. variations in variable product).
	add_filter(
		'woocommerce_get_children',
		function ( $children, $product ) use ( $alt_product ) {
			/** @var list<int>   $children */
			/** @var \WC_Product $product  */

			if ( $product->get_id() !== $alt_product->get_id() ) {
				return $children;
			}
			$control = $alt_product->get_control();
			return ! empty( $control ) ? $control->get_children() : $children;
		},
		10,
		2
	);

	// Use control ID in single screen’s add to cart action.
	$use_control_id_in_add_to_cart = function () use ( &$alt_product ) {
		$previous_global_product         = null;
		$use_control_in_add_to_cart      = function () use ( &$alt_product, &$previous_global_product ) {
			/** @var \WC_Product|null */
			global $product;
			if ( empty( $product ) || $product->get_id() !== $alt_product->get_id() ) {
				return;
			}
			$previous_global_product = $product;
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
			$product = $alt_product->get_control();
		};
		$undo_use_control_in_add_to_cart = function () use ( &$previous_global_product ) {
			/** @var \WC_Product|null */
			global $product;
			if ( null === $previous_global_product ) {
				return;
			}
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
			$product                 = $previous_global_product;
			$previous_global_product = null;
		};
		foreach ( array_keys( wc_get_product_types() ) as $type ) {
			add_action( "woocommerce_{$type}_add_to_cart", $use_control_in_add_to_cart, 1 );
			add_action( "woocommerce_{$type}_add_to_cart", $undo_use_control_in_add_to_cart, 99 );
		}
	};
	if ( did_action( 'init' ) || doing_action( 'init' ) ) {
		$use_control_id_in_add_to_cart();
	} else {
		add_action( 'init', $use_control_id_in_add_to_cart );
	}

	// Add control ID in WooCommerce’s cart.
	add_action(
		'woocommerce_add_to_cart_product_id',
		fn( $id ) => $id === $alt_product->get_id() ? $alt_product->get_control_id() : $id
	);

	// Make sure we use control’s link.
	$fix_link = function ( $permalink, $post ) use ( &$fix_link, &$alt_product ) {
		/** @var string   $permalink */
		/** @var \WP_Post $post      */

		$post_id = $post->ID;
		if ( $post_id !== $alt_product->get_id() ) {
			return $permalink;
		}

		remove_filter( 'post_type_link', $fix_link, 10 );
		$permalink = get_permalink( $alt_product->get_control_id() );
		add_filter( 'post_type_link', $fix_link, 10, 2 );
		return $permalink;
	};
	add_filter( 'post_type_link', $fix_link, 10, 2 );

	// Add additional info tab on products if needed.
	add_filter(
		'woocommerce_product_tabs',
		function ( $tabs ) use ( &$alt_product ) {
			/** @var array<string,mixed> $tabs */

			if ( get_the_ID() !== $alt_product->get_id() ) {
				return $tabs;
			}

			if ( isset( $tabs['additional_information'] ) ) {
				return $tabs;
			}

			$control = $alt_product->get_control();
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			if ( $control && ( $control->has_attributes() || apply_filters( 'wc_product_enable_dimensions_display', $control->has_weight() || $control->has_dimensions() ) ) ) {
				$tabs['additional_information'] = array(
					'title'    => _x( 'Additional information', 'text (woocommerce)', 'nelio-ab-testing' ),
					'priority' => 20,
					'callback' => 'woocommerce_product_additional_information_tab',
				);
			}

			return $tabs;
		}
	);

	// Use control attributes (e.g. variation types in variable product).
	add_filter(
		'woocommerce_product_get_attributes',
		function ( $attributes, $product ) use ( $alt_product ) {
			/** @var array<mixed> $attributes */
			/** @var \WC_Product  $product    */

			if ( $product->get_id() !== $alt_product->get_id() ) {
				return $attributes;
			}
			$control = $alt_product->get_control();
			return ! empty( $control ) ? $control->get_attributes() : $attributes;
		},
		10,
		2
	);

	// Use appropriate values for attributes that are a taxonomy.
	add_filter(
		'woocommerce_get_product_terms',
		function ( $terms, $product_id, $taxonomy, $args ) use ( $alt_product ) {
			/** @var list<\WP_Term> $terms      */
			/** @var int            $product_id */
			/** @var string         $taxonomy   */
			/** @var array<mixed>   $args       */

			if ( 0 !== strpos( $taxonomy, 'pa_' ) ) {
				return $terms;
			}
			if ( $alt_product->get_id() !== $product_id ) {
				return $terms;
			}
			return wc_get_product_terms( $alt_product->get_control_id(), $taxonomy, $args );
		},
		10,
		4
	);

	// Use original product’s stock status.
	add_filter(
		'woocommerce_product_get_stock_status',
		function ( $in_stock, $item ) use ( $alt_product ) {
			$product_id = get_product_id( $item );
			if ( $alt_product->get_id() !== $product_id ) {
				return $in_stock;
			}
			$control = $alt_product->get_control();
			return ! empty( $control ) ? $control->get_stock_status() : $in_stock;
		},
		10,
		2
	);

	use_control_reviews_in_alternative( $alt_attrs, $control_attrs, $experiment_id, $alternative_id );
}

/**
 * Returns the alternative product.
 *
 * @param TAttributes $alternative   Alternative attributes.
 * @param int         $control_id    Control product’s ID.
 * @param int         $experiment_id Experiment ID.
 *
 * @return \Nelio_AB_Testing\WooCommerce\Experiment_Library\Product_Experiment\IRunning_Alternative_Product The product.
 */
function get_alt_product( $alternative, $control_id, $experiment_id ) {
	$alt_post_id = absint( $alternative['postId'] ?? 0 );
	if ( $alt_post_id === $control_id ) {
		return new Running_Control_Product( $control_id, $experiment_id );
	}

	if ( is_v1_alternative( $alternative ) ) {
		return new Running_Alternative_Product_V1( $alternative, $control_id, $experiment_id );
	}

	if ( is_v2_alternative( $alternative ) ) {
		return new Running_Alternative_Product_V2( $alternative, $control_id, $experiment_id );
	}

	/** @var TWC_Product_Alternative_Attributes $alternative */
	return new Running_Alternative_Product( $alternative, $control_id, $experiment_id );
}

/**
 * Adds hooks to use control reviews in alternative product.
 *
 * @param TWC_Product_Alternative_Attributes|TWC_Product_Control_Attributes $alternative   Alternative.
 * @param TWC_Product_Control_Attributes                                    $control       Control.
 * @param int                                                               $experiment_id Experiment ID.
 * @param string                                                            $alternative_id Alternative ID.
 *
 * @return void
 */
function use_control_reviews_in_alternative( $alternative, $control, $experiment_id, $alternative_id ) {
	// Use control appropriate reviews.
	$loader = new Control_Comments_Loader( $alternative, $control, $experiment_id, $alternative_id );
	$loader->init();

	$control_product_id     = $control['postId'];
	$alternative_product_id = $alternative['postId'];

	// Show appropriate review count.
	add_filter(
		'woocommerce_product_get_review_count',
		function ( $count, $product ) use ( $control_product_id, $alternative_product_id ) {
			/** @var int         $count   */
			/** @var \WC_Product $product */

			if ( $product->get_id() !== $alternative_product_id ) {
				return $count;
			}
			$control = wc_get_product( $control_product_id );
			return ! empty( $control ) ? $control->get_review_count() : $count;
		},
		10,
		2
	);

	// Show appropriate review count.
	add_filter(
		'woocommerce_product_get_rating_counts',
		function ( $count, $product ) use ( $control_product_id, $alternative_product_id ) {
			/** @var int         $count   */
			/** @var \WC_Product $product */

			if ( $product->get_id() !== $alternative_product_id ) {
				return $count;
			}
			$control = wc_get_product( $control_product_id );
			return ! empty( $control ) ? $control->get_rating_counts() : $count;
		},
		10,
		2
	);

	// Show appropriate review average.
	add_filter(
		'woocommerce_product_get_average_rating',
		function ( $count, $product ) use ( $control_product_id, $alternative_product_id ) {
			/** @var int         $count   */
			/** @var \WC_Product $product */

			if ( $product->get_id() !== $alternative_product_id ) {
				return $count;
			}
			$control = wc_get_product( $control_product_id );
			return ! empty( $control ) ? $control->get_average_rating() : $count;
		},
		10,
		2
	);
}
