<?php
declare( strict_types=1 );

namespace FiboFilters\Config;

use FiboFilters\DI\ContainerWrapper;
use FiboFilters\Filtering\Front;
use FiboFilters\Filters\FilterFactory;
use FiboFilters\Filters\FilterSources;
use FiboFilters\Filters\StoreFilters;
use FiboFilters\Indexer\BuildInfo;
use FiboFilters\Indexer\SourceQueryFactory;
use FiboFilters\Integrations\Multilingual;
use FiboFilters\Integrations\Themes;
use FiboFilters\InvalidActionException;
use FiboFilters\InvalidTestException;
use FiboFilters\Settings\Storage;
use FiboFilters\Util\Helpers;
use FiboFilters\WooCommerce\Helper as WooCommerceHelper;

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

/**
 * Class Troubleshooting
 */
class Troubleshooting {

	const TRANSIENT_RESULTS_KEY      = 'fibofilters_troubleshooting_tests_results';
	const DISMISSED_TESTS_OPTION_KEY = 'fibofilters_dismissed_tests';


	/** @var Front */
	protected $front;

	/** @var Multilingual */
	private $multilingual;

	/** @var StoreFilters */
	private $store_filters;

	/** @var WooCommerceHelper */
	private $wc_helper;

	/** @var Themes */
	private $themes;

	/** @var BuildInfo */
	private $build_info;

	/** @var FilterSources */
	private $filter_sources;

	/** @var FilterFactory */
	private $filter_factory;

	/** @var Storage */
	private $storage;

	public function __construct(
		Front $front,
		Multilingual $multilingual,
		WooCommerceHelper $wc_helper,
		StoreFilters $store_filters,
		Themes $themes,
		BuildInfo $build_info,
		FilterSources $filter_sources,
		FilterFactory $filter_factory,
		Storage $storage
	) {
		$this->front          = $front;
		$this->multilingual   = $multilingual;
		$this->wc_helper      = $wc_helper;
		$this->store_filters  = $store_filters;
		$this->themes         = $themes;
		$this->build_info     = $build_info;
		$this->filter_sources = $filter_sources;
		$this->filter_factory = $filter_factory;
		$this->storage        = $storage;
	}

	public function init(): void {
		add_filter( 'fibofilters/admin_script/config', [ $this, 'add_troubleshooting_data' ] );
	}

	public function add_troubleshooting_data( array $config ): array {
		$config['troubleshooting'] = [
			'test_results'     => [],
			'async_tests'      => [],
			'in_browser_tests' => [],
		];

		$tests           = $this->get_tests();
		$dismissed_tests = $this->get_dismissed_tests();

		// Direct tests.
		if ( ! empty( $tests['direct'] ) && is_array( $tests['direct'] ) ) {
			foreach ( $tests['direct'] as $test ) {
				// Skip dismissed tests.
				if ( is_string( $test['test'] ) && in_array( $test['test'], $dismissed_tests, true ) ) {
					continue;
				}

				if ( is_string( $test['test'] ) ) {
					try {
						$result = $this->get_test_result( $test['test'] );
						if ( $result['status'] === 'critical' ) {
							$config['troubleshooting']['test_results'][] = $result;
						}
						continue;
					} catch ( InvalidTestException $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
						// Do nothing.
					}
				}

				if ( is_callable( $test['test'] ) ) {
					$result = $this->perform_test( $test['test'] );
					if ( $result['status'] === 'critical' ) {
						$config['troubleshooting']['test_results'][] = $result;
					}
				}
			}
		}

		// Async and in-browser tests.
		$tests_results = get_transient( self::TRANSIENT_RESULTS_KEY );
		foreach (
			[
				[
					'type' => 'async',
				],
				[
					'type' => 'in_browser',
				],
			] as $group
		) {
			if ( ! empty( $tests[ $group['type'] ] ) && is_array( $tests[ $group['type'] ] ) ) {
				$tests_in_group = wp_list_pluck( $tests[ $group['type'] ], 'test' );
				if ( ! empty( $tests_results ) && is_array( $tests_results ) ) {
					foreach ( $tests_results as $test ) {
						if ( ! in_array( $test['test'], $tests_in_group, true ) ) {
							continue;
						}
						// Skip dismissed tests.
						if ( is_string( $test['test'] ) && in_array( $test['test'], $dismissed_tests, true ) ) {
							continue;
						}
						if ( ! empty( $test['status'] ) && $test['status'] === 'critical' ) {
							$config['troubleshooting']['test_results'][] = $test;
						}
					}
				}

				// Pass tests without result to frontend. They will be launched once the settings page loads.
				foreach ( $tests[ $group['type'] ] as $test ) {
					// Skip dismissed tests.
					if ( is_string( $test['test'] ) && in_array( $test['test'], $dismissed_tests, true ) ) {
						continue;
					}
					if ( is_string( $test['test'] ) && ! isset( $tests_results[ $test['test'] ] ) ) {
						$config['troubleshooting'][ $group['type'] . '_tests' ][] = [
							'test'      => $test['test'],
							'completed' => false,
							'data'      => $group['type'] === 'in_browser' ? $this->get_in_browser_test_data( $test['test'] ) : [],
						];
					}
				}
			}
		}

		return $config;
	}

	/**
	 * Return a set of tests
	 *
	 * @return array The list of tests to run.
	 */
	public function get_tests(): array {
		$tests = [
			'direct'     => [
				[
					'label' => _x( 'Incompatible plugins', '[admin]', 'fibofilters' ),
					'test'  => 'incompatible_plugins',
				],
				[
					'label' => _x( 'Multilingual plugin', '[admin]', 'fibofilters' ),
					'test'  => 'multilingual_plugins',
				],
				[
					'label' => _x( 'Polylang not supported URL modifications', '[admin]', 'fibofilters' ),
					'test'  => 'polylang_not_supported_url_modifications',
				],
				[
					'label' => _x( 'Inconsistent tax settings', '[admin]', 'fibofilters' ),
					'test'  => 'inconsistent_tax_settings',
				],
				[
					'label' => _x( 'Range modified by fibofilters/filter/get_data', '[admin]', 'fibofilters' ),
					'test'  => 'range_deprecated_filtering',
				],
				[
					'label' => _x( 'FSE Theme', '[admin]', 'fibofilters' ),
					'test'  => 'fse_theme',
				],
				[
					'label' => _x( 'Theme integration disabled', '[admin]', 'fibofilters' ),
					'test'  => 'theme_integration_disabled',
				],
				[
					'label' => _x( 'Storage errors', '[admin]', 'fibofilters' ),
					'test'  => 'storage_errors',
				],
			],
			'async'      => [
				[
					'label' => _x( 'Incorrect product configuration', '[admin]', 'fibofilters' ),
					'test'  => 'incorrect_product_configuration',
				],
				[
					'label' => _x( 'Descriptors compression', '[admin]', 'fibofilters' ),
					'test'  => 'descriptors_compression',
				],
				[
					'label' => _x( 'Double canonicals', '[admin]', 'fibofilters' ),
					'test'  => 'double_canonicals',
				],
				[
					'label' => _x( 'Incorrect base product IDs', '[admin]', 'fibofilters' ),
					'test'  => 'incorrect_base_product_ids',
				],
				[
					'label' => _x( 'Invalid filter configuration', '[admin]', 'fibofilters' ),
					'test'  => 'invalid_filter_configuration',
				],
				[
					'label' => _x( 'LookUp Table', '[admin]', 'fibofilters' ),
					'test'  => 'wc_attributes_lookup',
				],
			],
			'in_browser' => [
				[
					'label' => _x( 'Descriptors file fetch failed', '[admin]', 'fibofilters' ),
					'test'  => 'descriptors_fetch_failed',
				],
			],
		];

		return apply_filters( 'fibofilters/troubleshooting/tests', $tests );
	}

	/**
	 * @throws InvalidTestException Invalid test exception.
	 */
	public function get_test_result( string $test ): array {
		$test_function = sprintf( 'get_test_%s', $test );

		if ( method_exists( $this, $test_function ) && is_callable( [ $this, $test_function ] ) ) {
			return $this->perform_test(
				[ $this, $test_function ] // @phpstan-ignore-line
			);
		} else {
			throw new InvalidTestException( 'There is no such test.' );
		}
	}

	public function get_in_browser_test_data( string $test ): array {
		$test_data_function = sprintf( 'get_in_browser_test_data_%s', $test );

		if ( method_exists( $this, $test_data_function ) && is_callable( [ $this, $test_data_function ] ) ) {
			return $this->$test_data_function();
		}

		return [];
	}

	public function save_test_result( array $test_result ): void {
		$this->store_result( $test_result );
	}

	/**
	 * Test for incompatible plugins
	 *
	 * @return array The test result.
	 */
	public function get_test_incompatible_plugins() {
		$result = [
			'label'       => _x( 'You are using one or more incompatible plugins', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'incompatible_plugins',
			'dismissible' => true,
		];

		$errors = [];

		// FacetWP.
		if ( class_exists( 'FacetWP' ) ) {
			/* translators: 1: Plugin full name */
			$errors[] = sprintf( _x( 'You use the %s plugin, which may cause errors in the filter results returned by our plugin.', '[admin]', 'fibofilters' ), '<strong>FacetWP</strong>', Env::$plugin_full_name );
		}

		// WP Engine AI Toolkit.
		if ( defined( 'WPE_SMART_SEARCH_VERSION' ) ) {
			/* translators: 1: Plugin full name */
			$errors[] = sprintf( _x( 'You use the %s plugin, which may cause errors in the filter results returned by our plugin.', '[admin]', 'fibofilters' ), '<strong>WP Engine AI Toolkit</strong>', Env::$plugin_full_name );
		}

		// B2B for WooCommerce by Addify.
		if ( class_exists( 'Addify_B2B_Plugin' ) ) {
			/* translators: 1: Plugin full name */
			$errors[] = sprintf( _x( 'You use the %s plugin, which may cause errors in the filter results returned by our plugin. We’re exploring the possibility of future integration.', '[admin]', 'fibofilters' ), '<strong>B2B for WooCommerce</strong>', Env::$plugin_full_name );
		}

		// B2BKing.
		if ( class_exists( 'B2bking' ) ) {
			/* translators: 1: Plugin full name */
			$errors[] = sprintf( _x( 'You use the %s plugin, which may cause errors in the filter results returned by our plugin. We’re exploring the possibility of future integration.', '[admin]', 'fibofilters' ), '<strong>B2BKing</strong>', Env::$plugin_full_name );
		}

		// Sort Products For WooCommerce.
		if ( defined( 'EX_SPBC_VERSION' ) ) {
			$errors[] = sprintf(
			/* translators: 1: Plugin full name, 2: Documentation link */
				_x(
					'You use the %1$s plugin. At the moment, FiboFilters is not integrated with it. Please check what is currently possible with product sorting in our <a href="%2$s" target="_blank">documentation</a>.',
					'[admin]',
					'fibofilters'
				),
				'<strong>Sort Products For WooCommerce</strong>',
				'https://fibofilters.com/documentation/how-it-works/product-sorting/'
			);
		}

		if ( ! empty( $errors ) ) {
			$result['description'] = wpautop( join( "\r\n", $errors ) );
			$result['status']      = 'critical';
		}

		return $result;
	}

	/**
	 * Test for multilingual plugins
	 *
	 * @return array The test result.
	 */
	public function get_test_multilingual_plugins() {
		$result = [
			'label'       => _x( 'You are using a multilingual plugin', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'multilingual_plugins',
			'dismissible' => true,
		];

		$errors = [];

		$provider_name = $this->multilingual->get_provider_name();
		if ( $provider_name !== 'not set' && $provider_name !== 'WPML' && $provider_name !== 'Polylang' ) {
			/* translators: 1: Plugin full name 2: FiboFilters full name */
			$errors[] = sprintf( _x( 'You are using the %1$s plugin. The current version of %2$s does not support multiple languages based on this plugin and FiboFilters may not work properly.', '[admin]', 'fibofilters' ), '<strong>' . $provider_name . '</strong>', Env::$plugin_full_name );
		}

		if ( ! empty( $errors ) ) {
			$result['description'] = wpautop( join( "\r\n", $errors ) );
			$result['status']      = 'critical';
		}

		return $result;
	}

	/**
	 * Test for not supported "URL modifications" option in Polylang
	 *
	 * @return array The test result.
	 */
	public function get_test_polylang_not_supported_url_modifications() {
		$result = [
			'label'       => _x( 'Incompatible Polylang plugin setting', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'polylang_not_supported_url_modifications',
			'dismissible' => true,
		];

		if ( $this->multilingual->get_provider_name() === 'Polylang' && function_exists( 'PLL' ) && PLL()->options['force_lang'] === 0 ) {
			$result['description'] = '<p>' . _x( 'You are using Polylang with the "URL modifications" option set to "The language is set from content" - this option is not compatible with FiboFilters and it will not work correctly until you change the option to another one.', '[admin]', 'fibofilters' ) . '</p>';
			$result['status']      = 'critical';
		}

		return $result;
	}

	/**
	 * Test for incorrect product configuration
	 */
	public function get_test_incorrect_product_configuration(): array {
		$result = [
			'label'       => _x( 'We found variable products with ambiguous stock status', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'incorrect_product_configuration',
			'dismissible' => true,
		];

		if ( get_option( 'woocommerce_hide_out_of_stock_items' ) !== 'yes' ) {
			$this->store_result( $result );

			return $result;
		}

		$edit_links = [];

		$hint  = _x( 'The following products will not be indexed and, consequently, they will not appear in the product catalog because you have enabled the WooCommerce option "Hide out of stock items from the catalog"', '[admin]', 'fibofilters' );
		$hint .= ' ' . _x( 'We recommend disabling "Stock management" for parents if all variations manage stock independently. After doing it, click "Recheck the status" and this message should disappear.', '[admin]', 'fibofilters' );

		/**
		 * 1. We are looking for products:
		 * - type: variable,
		 * - manage stock (for parent): yes,
		 * - parent: in stock,
		 * - without visible children
		 */
		$products = wc_get_products(
			[
				'type'         => 'variable',
				'manage_stock' => true,
				'stock_status' => 'instock',
				'limit'        => - 1,
			]
		);

		foreach ( $products as $product ) {
			/** @var \WC_Product $product */
			if ( empty( $product->get_visible_children() ) ) {
				$edit_link = get_edit_post_link( $product->get_id() );
				if ( ! empty( $edit_link ) ) {
					$edit_links[] = make_clickable( $edit_link );
				}
			}
		}

		if ( ! empty( $edit_links ) ) {
			$result['status']       = 'critical';
			$result['description'] .= '<p>' . _x( 'There are variable products whose parent has non-zero stock, but none of the variants are visible - this may be because they are out of stock or are not enabled:', '[admin]', 'fibofilters' ) . '</p>';
			$result['description'] .= '<p>' . $hint . '</p>';
			$result['description'] .= wpautop( join( "\r\n", $edit_links ) );
		}

		/**
		 * 2. We are looking for products:
		 * - type: variable,
		 * - manage stock (for parent): yes,
		 * - parent: allow backorders,
		 * - without visible children
		 */
		$products = wc_get_products(
			[
				'type'         => 'variable',
				'manage_stock' => true,
				'stock_status' => 'onbackorder',
				'limit'        => - 1,
			]
		);

		$edit_links = [];

		foreach ( $products as $product ) {
			/** @var \WC_Product $product */
			if ( empty( $product->get_visible_children() ) ) {
				$edit_link = get_edit_post_link( $product->get_id() );
				if ( ! empty( $edit_link ) ) {
					$edit_links[] = make_clickable( $edit_link );
				}
			}
		}

		if ( ! empty( $edit_links ) ) {
			$result['status']       = 'critical';
			$result['description'] .= '<p>' . _x( 'There are variable products that are not in stock, they have the status "on backorder", but none of the variants are visible:', '[admin]', 'fibofilters' ) . '</p>';
			$result['description'] .= '<p>' . $hint . '</p>';
			$result['description'] .= wpautop( join( "\r\n", $edit_links ) );
		}

		$this->store_result( $result );

		return $result;
	}

	/**
	 * Test for double canonicals
	 */
	public function get_test_double_canonicals(): array {
		$result = [
			'label'       => _x( "Duplicate canonical link detected in the website's HTML source", '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'double_canonicals',
			'dismissible' => true,
		];

		// Break early if the filter has been added.
		if ( has_filter( 'fibofilters/print_canonical' ) ) {
			$this->store_result( $result );

			return $result;
		}

		// Get product category list.
		$categories = get_terms(
			[
				'taxonomy' => 'product_cat',
			]
		);

		// The get_terms() function can also return a string or an object.
		if ( ! is_array( $categories ) || ! isset( $categories[0] ) ) {
			$this->store_result( $result );

			return $result;
		}

		// Skip if there are less than 2 categories.
		if ( count( $categories ) < 2 ) {
			$this->store_result( $result );

			return $result;
		}

		$category_url = get_term_link( $categories[0] );

		if ( wc_is_valid_url( $category_url ) ) {
			$cookies       = [];
			$timeout       = 10;
			$headers       = [];
			$authorization = Helpers::get_basic_auth_header();
			if ( ! empty( $authorization ) ) {
				$headers['Authorization'] = $authorization;
			}
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			$sslverify = apply_filters( 'https_local_ssl_verify', false );
			$response  = wp_remote_get( $category_url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
			$body      = wp_remote_retrieve_body( $response );

			if (
				wp_remote_retrieve_response_code( $response ) === 200 &&
				/**
				 * On the category page (without pagination or filters), our plugin will not add a canonical link.
				 * Therefore, if one is detected, it does not come from our plugin, which may result in duplicate canonical links.
				 */
				strpos( $body, 'rel="canonical"' ) !== false
			) {
				$result['status']       = 'critical';
				$result['description'] .= '<p>' . _x( 'Such a situation may cause search engines to ignore both canonical links and index these pages. To avoid this, we recommend disabling the addition of the canonical link by our plugin using the following filter:', '[admin]', 'fibofilters' ) . '</p>';
				$result['description'] .= "<p><code>add_filter('fibofilters/print_canonical', '__return_false');</code></p>";
			}
		}

		$this->store_result( $result );

		return $result;
	}

	/**
	 * Test for incorrect base product IDs
	 */
	public function get_test_incorrect_base_product_ids(): array {
		global $wp_the_query, $wp_query, $wp_filter, $fibofilters_troubleshooting_wp_query_clauses;

		$current_wp_the_query = $wp_the_query;
		$current_wp_query     = $wp_query;

		$result = [
			'label'       => _x( 'Incorrect base product IDs', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'incorrect_base_product_ids',
			'dismissible' => true,
		];

		$wp_the_query = new \WP_Query(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
		$wp_query     = $wp_the_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited

		add_filter( 'posts_clauses', [ $this, 'debug_wp_query_clauses' ], PHP_INT_MAX - 3, 2 );

		$main_query_ids     = $wp_the_query->query(
			[
				'post_type'                     => 'product',
				'fields'                        => 'ids',
				'fibofilters_log_posts_clauses' => '1',
			]
		);
		$main_query_clauses = $fibofilters_troubleshooting_wp_query_clauses;

		$args = array_merge(
			$wp_the_query->query_vars,
			[
				'paged'                            => 1,
				'posts_per_page'                   => - 1,
				'update_post_meta_cache'           => false,
				'update_post_term_cache'           => false,
				'cache_results'                    => true,
				'no_found_rows'                    => true,
				'nopaging'                         => true,
				'fields'                           => 'ids',
				'fibofilters_get_base_product_ids' => true,
			]
		);

		// If the filter is set, global $wp_query will be overridden in Request::get_base_product_ids_by_wp_query().
		// In that case, we need to override it here as well to get correct results.
		if ( apply_filters( 'fibofilters/request/base_product_ids/override_global_wp_query', false, $args, false ) ) {
			// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
			$wp_query          = new \WP_Query();
			$wp_the_query      = $wp_query;
			$base_products_ids = $wp_query->query( $args );
			// phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
		} else {
			$base_products_query = new \WP_Query( $args );
			$base_products_ids   = $base_products_query->posts;
		}

		$base_products_ids_query_clauses = $fibofilters_troubleshooting_wp_query_clauses;

		if ( count( $main_query_ids ) > 0 && $main_query_ids !== array_slice( $base_products_ids, 0, count( $main_query_ids ) ) ) {
			$doc_url = 'https://fibofilters.com/documentation/troubleshooting/incorrect-base-product-ids/';

			$result['status']       = 'critical';
			$result['description'] .= '<p>' . _x( 'We have detected that the list of base product IDs differs from the list of IDs in the main query, which may lead to an unwanted effect of loading products on the shop page after the filters are loaded.', '[admin]', 'fibofilters' ) . '</p>';
			// translators: 1: Documentation link.
			$result['description'] .= '<p>' . sprintf( _x( 'We encourage you to add the following filter, which should resolve this issue. You can read more about it in our <a href="%s" target="_blank">documentation</a>.', '[admin]', 'fibofilters' ), $doc_url ) . '</p>';
			$result['description'] .= "<p><code>add_filter( 'fibofilters/request/base_product_ids/override_global_wp_query', '__return_true' );</code></p>";
			$result['description'] .= '<p>' . _x( 'Otherwise, please contact our support team to resolve this issue. We encourage you to include the report below, as it will help us with debugging.', '[admin]', 'fibofilters' ) . '</p>';
			$result['description'] .= '<textarea readonly style="margin-top: 1em; min-width: 100%; min-height: 250px; font-size: 12px;">';
			$result['description'] .= _x( 'Main query clauses:', '[admin]', 'fibofilters' ) . "\n";
			$result['description'] .= "==================================\n";
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
			$result['description'] .= print_r( $main_query_clauses, true );
			$result['description'] .= "\n\n";

			$result['description'] .= _x( 'Base product IDs query clauses:', '[admin]', 'fibofilters' ) . "\n";
			$result['description'] .= "==================================\n";
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
			$result['description'] .= print_r( $base_products_ids_query_clauses, true );
			$result['description'] .= "\n\n";

			$hooks = [ 'pre_get_posts', 'posts_clauses' ];

			foreach ( $hooks as $hook ) {
				if ( isset( $wp_filter[ $hook ] ) && is_a( $wp_filter[ $hook ], 'WP_Hook' ) ) {
					$result['description'] .= 'Callbacks for "' . $hook . "\":\n";
					$result['description'] .= "==================================\n";

					foreach ( $wp_filter[ $hook ]->callbacks as $priority => $callbacks ) {
						$result['description'] .= "Priority $priority:\n";
						foreach ( $callbacks as $callback ) {
							$file = '';
							$line = '';
							try {
								if ( is_string( $callback['function'] ) ) {
									// Named function.
									$ref  = new \ReflectionFunction( $callback['function'] );
									$file = $ref->getFileName();
									$line = $ref->getStartLine();
								} elseif ( is_array( $callback['function'] ) && count( $callback['function'] ) === 2 ) {
									// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
									// [Object/Class, 'method'].
									$ref  = new \ReflectionMethod( $callback['function'] [0], $callback['function'] [1] );
									$file = $ref->getFileName();
									$line = $ref->getStartLine();
								} elseif ( $callback['function'] instanceof \Closure ) {
									// Anonymous function.
									$ref  = new \ReflectionFunction( $callback['function'] );
									$file = $ref->getFileName();
									$line = $ref->getStartLine();
								}
							} catch ( \ReflectionException $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
							}

							if ( is_array( $callback['function'] ) ) {
								$fn = is_object( $callback['function'][0] )
									? get_class( $callback['function'][0] ) . '->' . $callback['function'][1]
									: $callback['function'][0] . '::' . $callback['function'][1];
							} elseif ( is_string( $callback['function'] ) ) {
								$fn = $callback['function'];
							} else {
								$fn = 'Closure or unknown type';
							}

							$result['description'] .= '  - ' . esc_html( $fn );
							if ( ! empty( $file ) && ! empty( $line ) ) {
								$result['description'] .= " (in $file on line $line)";
							}
							$result['description'] .= "\n";
						}
						$result['description'] .= "\n";
					}
				}
			}

			// Check if $base_products_ids contains all IDs from $main_query_ids, skipping order.
			$main_query_ids_diff       = array_diff( $main_query_ids, $base_products_ids );
			$is_main_query_ids_in_base = empty( $main_query_ids_diff );

			$result['description'] .= _x( 'Are the first page IDs of the main query included in the list of base product IDs?', '[admin]', 'fibofilters' ) . "\n";
			$result['description'] .= "==================================\n";
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
			$result['description'] .= $is_main_query_ids_in_base ? 'Yes' : 'No';
			if ( ! $is_main_query_ids_in_base ) {
				// Show how many IDs (in %) are missing.
				$missing_count          = count( $main_query_ids_diff );
				$total_count            = count( $main_query_ids );
				$result['description'] .= '. ';
				$result['description'] .= sprintf(
					// translators: 1: Missing IDs count, 2: Total IDs count.
					_x( 'Missing IDs: %1$d out of %2$d', '[admin]', 'fibofilters' ),
					$missing_count,
					$total_count
				);
			}
			$result['description'] .= "\n\n";

			$result['description'] .= _x( 'Main query IDs', '[admin]', 'fibofilters' ) . "\n";
			$result['description'] .= "==================================\n";
			$result['description'] .= '[' . implode( ', ', $main_query_ids ) . "]\n\n";

			$result['description'] .= _x( 'First page of base product IDs', '[admin]', 'fibofilters' ) . "\n";
			$result['description'] .= "==================================\n";
			$result['description'] .= '[' . implode( ', ', array_slice( $base_products_ids, 0, count( $main_query_ids ) ) ) . "]\n\n";

			$result['description'] .= '</textarea>';
		}

		$wp_the_query = $current_wp_the_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
		$wp_query     = $current_wp_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited

		$this->store_result( $result );

		return $result;
	}

	/**
	 * Test for descriptors compression
	 *
	 * Large JSON files should be compressed (Gzip or Brotli)
	 */
	public function get_test_descriptors_compression(): array {
		$result = [
			'label'       => _x( 'No compression when fetching a descriptor file', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'descriptors_compression',
			'dismissible' => true,
		];

		$descriptors_endpoint = $this->front->get_config_value( 'descriptors_endpoint', '' );

		if ( strpos( $descriptors_endpoint, '.json' ) !== false ) {
			$cookies       = [];
			$timeout       = 10;
			$headers       = [
				'Cache-Control'   => 'no-cache',
				'Accept-Encoding' => 'gzip, deflate, br',
			];
			$authorization = Helpers::get_basic_auth_header();
			if ( ! empty( $authorization ) ) {
				$headers['Authorization'] = $authorization;
			}
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			$sslverify = apply_filters( 'https_local_ssl_verify', false );
			$response  = wp_remote_get( $descriptors_endpoint, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );

			/**
			 * Raise the alarm if:
			 * - there is no compression
			 * - and the size of the descriptor file is greater than 500kB
			 */
			if (
				wp_remote_retrieve_response_code( $response ) === 200 &&
				empty( wp_remote_retrieve_header( $response, 'content-encoding' ) ) &&
				strlen( wp_remote_retrieve_body( $response ) ) > 500000
			) {
				$result['status'] = 'critical';

				$result['description'] .= '<p>' . _x( "On the customer’s first visit to your store, it's necessary to download a file with descriptors that are necessary for the filtering process. It's about this file:", '[admin]', 'fibofilters' ) . '</p>';
				$result['description'] .= '<p><code>' . esc_url( $descriptors_endpoint ) . '</code></p>';
				$result['description'] .= '<p>';
				$result['description'] .= _x( 'Your server should use compression algorithm to reduce the size the file. For some reason the file <b>is not compressed</b> in your case.', '[admin]', 'fibofilters' );
				$result['description'] .= ' ' . _x( 'We recommend contacting your hosting provider and asking them to enable compression of these types of files.', '[admin]', 'fibofilters' );
				$result['description'] .= ' ' . _x( 'Compression can reduce the size of the file by up to 70%-80%.', '[admin]', 'fibofilters' );
				$result['description'] .= '</p>';
			}
		}

		$this->store_result( $result );

		return $result;
	}

	/**
	 * Check it there are inconsistent tax settings.
	 *
	 * To avoid possible rounding errors, prices should be entered and displayed consistently
	 * in all locations either including, or excluding taxes.
	 * https://woocommerce.com/document/setting-up-taxes-in-woocommerce/#rounding
	 *
	 * @return array
	 */
	public function get_test_inconsistent_tax_settings(): array {
		$result = [
			'label'       => _x( 'Inconsistent tax settings', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'inconsistent_tax_settings',
			'dismissible' => true,
		];

		$price_display_mode = $this->wc_helper->get_price_display_mode();
		if ( ! empty( $price_display_mode ) && in_array( $price_display_mode, [ 'excl', 'incl' ] ) ) {
			$settings_url     = esc_url( admin_url( 'admin.php?page=wc-settings&tab=tax#woocommerce_tax_display_cart' ) );
			$doc_url          = 'https://woocommerce.com/document/setting-up-taxes-in-woocommerce/#rounding';
			$result['status'] = 'critical';

			$result['description'] .= '<p><b>' . _x( 'Some of these following tax settings are mixed:', '[admin]', 'fibofilters' ) . '</b></p>';
			$result['description'] .= '<ul>';
			$result['description'] .= '<li>' . _x( 'Prices entered with tax', '[admin]', 'fibofilters' ) . '</li>';
			$result['description'] .= '<li>' . _x( 'Display prices in the store', '[admin]', 'fibofilters' ) . '</li>';
			$result['description'] .= '<li>' . _x( 'Display prices during cart and checkout', '[admin]', 'fibofilters' ) . '</li>';
			$result['description'] .= '</ul>';

			$result['description'] .= '<p>';
			$result['description'] .= _x( 'To avoid possible rounding errors, prices should be entered and displayed consistently in all locations either including, or excluding taxes.', '[admin]', 'fibofilters' ) . ' ';
			$result['description'] .= _x( 'FiboFilters can handle such cases, but for better performance, we recommend not to mismatch tax settings.', '[admin]', 'fibofilters' ) . ' ';
			$result['description'] .= sprintf( _x( 'Go to the <a href="%s" target="_blank">WooCommerce tax settings</a> to fix it.', '[admin]', 'fibofilters' ), $settings_url ) . ' ';
			$result['description'] .= sprintf( _x( 'Learn more about rounding tax error in the <a href="%s" target="_blank">WooCommerce documentation</a>.', '[admin]', 'fibofilters' ), $doc_url );
			$result['description'] .= '</p>';
		}

		return $result;
	}

	/**
	 * Check it there are Range filter modified by fibofilters/filter/get_data hook
	 *
	 * @return array
	 */
	public function get_test_range_deprecated_filtering(): array {
		$result = [
			'label'       => _x( 'Possible use of a deprecated filter', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'range_deprecated_filtering',
			'dismissible' => true,
		];

		$types = $this->store_filters->get_all_filter_url_types();

		if ( in_array( 'range', $types, true ) && has_filter( 'fibofilters/filter/get_data' ) ) {
			$doc_url               = 'https://fibofilters.com/documentation/filter-types/number-range/';
			$result['status']      = 'critical';
			$result['description'] = '<p>' . _x( 'We have detected that you have defined Range filter(s) and are using the <code>fibofilters/filter/get_data</code> hook. If you are using this hook to modify settings related to number formatting, please continue reading. Otherwise, you can ignore this message and dismiss it.', '[admin]', 'fibofilters' ) . '</p>';
			/* translators: %s: Documentation URL */
			$result['description'] .= '<p>' . sprintf( _x( 'The Range filter has been improved, allowing you to change the number format directly from its settings. You can read more about this in our <a href="%s" target="_blank">documentation</a>.', '[admin]', 'fibofilters' ), $doc_url ) . '</p>';
		}

		return $result;
	}

	/**
	 * Check it current theme is Full Site Editing theme
	 *
	 * @return array
	 */
	public function get_test_fse_theme(): array {
		$result = [
			'label'       => _x( 'Full Site Editing theme detected', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'fse_theme',
			'dismissible' => true,
		];

		$theme = wp_get_theme();

		if ( $theme->is_block_theme() ) {
			$doc_url          = 'https://fibofilters.com/documentation/tips-tricks/fse-full-site-editing/';
			$result['status'] = 'critical';
			/* translators: %s: Documentation URL */
			$result['description'] .= '<p style="margin-bottom: 1em">' . sprintf( _x( 'We\'ve detected that you\'re using a Full Site Editing theme, but unfortunately, our filters are not yet compatible with this type of theme. You can learn more in our <a href="%s" target="_blank">documentation</a>.', '[admin]', 'fibofilters' ), $doc_url ) . '</p>';
			$result['description'] .= '<div class="components-notice is-info is-dismissible"><div class="components-notice__content">' . _x( 'Work is currently underway to integrate FiboFilters with the Interactivity API and FSE. If you have a site built with FSE and the Interactivity API, please contact us at <a href="https://fibofilters.com/contact/" target="_blank">https://fibofilters.com/contact/</a>. We need real-world examples of such stores to speed up the integration.', '[admin]', 'fibofilters' ) . '</div></div>';
		}

		return $result;
	}

	/**
	 * Check it integration with the theme is disabled
	 *
	 * @return array
	 */
	public function get_test_theme_integration_disabled(): array {
		$result = [
			/* translators: 1: Theme name */
			'label'       => _x( 'Seamless integration with %s is currently disabled!', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'theme_integration_disabled',
			'dismissible' => true,
		];

		$current_integration = $this->themes->get_current_integration();
		if ( empty( $current_integration ) ) {
			return $result;
		}
		if ( $this->themes->is_current_integration_enabled() ) {
			return $result;
		}

		// Break early if there are no filters.
		$filters = $this->store_filters->get_all_filters();
		if ( empty( $filters ) ) {
			return $result;
		}

		$result['status'] = 'critical';
		$result['label']  = sprintf( $result['label'], esc_html( $current_integration['name'] ) );

		if ( ! empty( $current_integration['args']['docs_url'] ) ) {
			/* translators: 1: Name of theme 2: Documentation URL 3: Name of theme */
			$result['description'] .= '<p>' . sprintf( _x( 'We detected that you\'re using the %1$s theme. FiboFilters offers <a href="%2$s" target="_blank">seamless integration with %3$s</a>, but it appears that this integration is currently disabled. As a result, filtering or product loading may not work correctly.', '[admin]', 'fibofilters' ), $current_integration['name'], $current_integration['args']['docs_url'], $current_integration['name'] ) . '</p>';
		} else {
			/* translators: 1: Name of theme 2: Name of theme */
			$result['description'] .= '<p>' . sprintf( _x( 'We detected that you\'re using the %1$s theme. FiboFilters offers seamless integration with %2$s, but it appears that this integration is currently disabled. As a result, filtering or product loading may not work correctly.', '[admin]', 'fibofilters' ), $current_integration['name'], $current_integration['name'] ) . '</p>';
		}
		$result['description'] .= '<p>' . _x( 'Please enable the integration by going to WooCommerce → FiboFilters → General → Enable integration.', '[admin]', 'fibofilters' ) . '</p>';

		return $result;
	}

	/**
	 * Check if the filter configuration is valid
	 *
	 * @return array
	 */
	public function get_test_invalid_filter_configuration(): array {
		$result = [
			/* translators: 1: Theme name */
			'label'       => _x( 'Invalid filter configuration', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'invalid_filter_configuration',
			'dismissible' => true,
		];

		// Break early if there are no filters.
		$filters = $this->store_filters->get_all_filters();
		if ( empty( $filters ) ) {
			return $result;
		}

		$filter_types = $this->filter_factory->get_filter_types();

		foreach ( $filters as $filter ) {
			/**
			 * Invalid filter type.
			 */
			$source = $this->filter_sources->get_source( $filter['source'], true );

			if ( $source->is_analyse_source_values() ) {
				$analysis_data = get_transient( Env::$plugin_prefix . 'sources_analyse_data' );

				if ( empty( $analysis_data[ $source->get_data_attr( 'value' ) ] ) ) {
					continue;
				}
			}

			$allowed_types = $source->get_data_attr( 'allowedTypes' );
			if ( is_array( $allowed_types ) && ! in_array( $filter['type'], $allowed_types, true ) ) {
				$type = array_values( wp_list_filter( $filter_types, [ 'value' => $filter['type'] ] ) );
				$type = ! empty( $type ) ? $type[0]['label'] : $filter['type'];

				$result['status']       = 'critical';
				$result['description'] .= '<p>';
				/* translators: 1: Filter name 2: Filter type */
				$result['description'] .= sprintf( _x( 'The filter %1$s is configured incorrectly. The filter type %2$s is not allowed for this source.', '[admin]', 'fibofilters' ), '<strong>' . $filter['label'] . '</strong>', '<strong>' . $type . '</strong>' );
				if ( count( $allowed_types ) === 1 ) {
					$result['description'] .= ' ' . _x( 'Only one type is available for this source, so please remove the filter and add it again — the correct type will be set automatically.', '[admin]', 'fibofilters' );
				}
				$result['description'] .= '</p>';
			}
		}
		$this->save_test_result( $result );

		return $result;
	}

	/**
	 *  Check WC Product Attributes Lookup Table for inconsistencies
	 *
	 * @return array
	 */
	public function get_test_wc_attributes_lookup(): array {
		$threshold_percent = 8.0;
		$result            = [
			/* translators: [admin] Health check label for Lookup table test */
			'label'       => _x( 'Mismatch – WC Product Attributes Lookup Table', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'wc_attributes_lookup',
			'dismissible' => true,
		];

		// TODO: Test disabled until it’s fixed. There’s a bug in the calculation method. #CU-86byv1efn.
		return $result;

		if ( ! defined( 'WC_VERSION' ) || version_compare( WC_VERSION, '9.1.0', '<' ) ) {
			return $result;
		}

		global $wpdb;

		$product_ids_look_up = $wpdb->get_col(
			"SELECT DISTINCT product_id FROM {$wpdb->prefix}wc_product_attributes_lookup"
		);

		if ( empty( $product_ids_look_up ) && is_wp_error( $product_ids_look_up ) ) {
			return $result;
		}

		/** @var SourceQueryFactory $fibofilters_source_query_factory */
		$fibofilters_source_query_factory = ContainerWrapper::get_instance()->get( SourceQueryFactory::class );
		$fibofilters_source_query         = $fibofilters_source_query_factory->get(
			'filters',
			[
				'context' => 'indexer',
			]
		);

		$products_ids_source_query = array_map(
			'intval',
			array_column( $fibofilters_source_query->get_data(), 'ID' )
		);

		// Approximate check: which IDs are missing in lookup vs Source Query.
		$lookup_count   = count( $product_ids_look_up );
		$expected_count = count( $products_ids_source_query );

		$missing_ids   = array_diff( $products_ids_source_query, $product_ids_look_up );
		$missing_count = (int) count( $missing_ids );

		$baseline_count        = $expected_count > 0 ? $expected_count : $lookup_count;
		$inconsistency_ratio   = $baseline_count > 0 ? ( $missing_count / $baseline_count ) : 0.0;
		$inconsistency_percent = round( $inconsistency_ratio * 100, 2 );

		$is_over_threshold = ( $inconsistency_percent > $threshold_percent );

		if ( ! $is_over_threshold ) {
			// All good — within tolerance.
			$this->store_result( $result );

			return $result;
		}

		$result['status']       = 'critical';
		$result['description'] .= '<p>' .
									sprintf(
									/* translators: 1: missing count, 2: expected count, 3: percent */
										_x( 'Missing <b>%1$d</b> product IDs out of <b>%2$d</b> expected products in the lookup table (<b>%3$s%% mismatch</b>).', '[admin]', 'fibofilters' ),
										$missing_count,
										$expected_count,
										number_format_i18n( $inconsistency_percent, 2 )
									) .
									'</p>';

		$result['description'] .= '<p>';
		$result['description'] .= sprintf(
			/* translators: %s: wc_product_attributes_lookup table */
			_x(
				'The <b>Product Attributes Lookup Table</b> %s is a special WooCommerce table used to speed up filtering. FiboFilters relies on this table during its indexing process, so missing products <b>may lead to incomplete or incorrect filter results</b>.',
				'[admin]',
				'fibofilters'
			),
			'(<i>wc_product_attributes_lookup</i>)',
		);

		$result['description'] .= '</p>';

		$result['description'] .= '<h3>' . esc_html_x( 'Potential causes:', '[admin]', 'fibofilters' ) . '</h3>';
		$result['description'] .= '<ul>';

		$result['description'] .= '<li>' . sprintf(
			/* translators: %s: wc_product_attributes_lookup table */
			esc_html_x(
				'A bulk import or product synchronization tool updates products but does not update the %s table.',
				'[admin]',
				'fibofilters'
			),
			'<i>wc_product_attributes_lookup</i>'
		) . '</li>';

		$result['description'] .= '<li>' . sprintf(
			/* translators: %s: wc_product_attributes_lookup table */
			esc_html_x(
				'Products were migrated or synced from another environment without copying or regenerating the %s table.',
				'[admin]',
				'fibofilters'
			),
			'<i>wc_product_attributes_lookup</i>'
		) . '</li>';

		$result['description'] .= '<li>' . esc_html_x(
			'WordPress Scheduled Actions are getting stuck or not running at all.',
			'[admin]',
			'fibofilters'
		) . '</li>';

		$result['description'] .= '</ul>';

		$result['description'] .= '<h3>' . _x( 'Solution #1:', '[admin]', 'fibofilters' ) . '</h3>';

		// Actionable recommendation – use a filter in functions.php to force regeneration.
		$result['description'] .= '<p>';
		$result['description'] .= esc_html_x(
			'To fix this issue, add the following filter to the functions.php file of your (child) theme:',
			'[admin]',
			'fibofilters'
		);
		$result['description'] .= '</p>';

		$code_example = "add_filter( 'fibofilters/indexer/attributes_lookup_table/should_regenerate', fn() => true );";

		$result['description'] .= '<pre><code>' . esc_html( $code_example ) . '</code></pre>';

		$result['description'] .= '<p>';
		$result['description'] .= sprintf(
			/* translators: %s: wc_product_attributes_lookup table */
			esc_html_x(
				'This will automatically regenerate the %s table every time the indexing process is run.',
				'[admin]',
				'fibofilters'
			),
			'<i>wc_product_attributes_lookup</i>'
		);
		$result['description'] .= '</p>';

		$result['description'] .= '<h3>' . _x( 'Solution #2:', '[admin]', 'fibofilters' ) . '</h3>';

		$result['description'] .= '<p>';
		$result['description'] .= _x(
			'You can also regenerate the Product Attributes Lookup Table manually. Simply go to <b>WooCommerce → Status → Tools → Regenerate the product attributes lookup table</b> and click Regenerate.',
			'[admin]',
			'fibofilters'
		);
		$result['description'] .= '</p>';

		$result['description'] .= '<p><b>' . esc_html_x( 'Worth knowing:', '[admin]', 'fibofilters' ) . '</b></p>';

		$result['description'] .= '<p>';
		$result['description'] .= _x(
			'Regenerating the Product Attributes Lookup Table can be time-consuming and prone to errors if your store has many products, variations, and attributes. WooCommerce has improved the performance of this process, but the optimization must be enabled manually. To turn it on, go to <b>WooCommerce → Settings → Products → Advanced</b> and enable “Optimized updates”.',
			'[admin]',
			'fibofilters'
		);
		$result['description'] .= '</p>';

		$this->store_result( $result );

		return $result;
	}

	/**
	 * Check if storage works correctly.
	 *
	 * @return array
	 */
	public function get_test_storage_errors(): array {
		$result = [
			'label'       => _x( 'Storage errors', '[admin]', 'fibofilters' ),
			'status'      => 'good',
			'description' => '',
			'actions'     => '',
			'test'        => 'storage_errors',
			'dismissible' => true,
		];

		$filters_backup = get_option( 'fibofilters_filters_backup' );

		if ( empty( $filters_backup ) ) {
			return $result;
		}

		if ( defined( 'FIBOFILTERS_STORAGE_ONLY' ) && FIBOFILTERS_STORAGE_ONLY === true ) {
			return $result;
		}

		$filters_storage = $this->storage->get( 'filters' );

		if ( empty( $filters_storage ) ) {
			$result['status'] = 'critical';

			$result['description'] .= '<p>' . _x( "To improve database performance, we're using a new table to store, among other things, filter settings. However, it seems that the migration process did not complete successfully.", '[admin]', 'fibofilters' ) . '</p>';
			$result['description'] .= '<p>' . _x( 'If the filters disappeared after a plugin update, please contact our support team to resolve the issue.', '[admin]', 'fibofilters' ) . '</p>';
		}

		return $result;
	}

	public function get_in_browser_test_data_descriptors_fetch_failed(): array {
		$index_status = $this->build_info->get_info( 'status' );
		$json_url     = $this->front->get_config_value( 'descriptors_json_url', '' );
		$rest_url     = $this->front->get_config_value( 'descriptors_endpoint', '' );

		return [
			'index_status' => $index_status,
			'json_url'     => $json_url,
			'rest_url'     => $rest_url,
		];
	}

	public function do_action( string $action, array $args ) {
		$method = sprintf( 'action_%s', str_replace( '-', '_', $action ) );
		if ( is_callable( [ $this, $method ] ) ) {
			return call_user_func( [ $this, $method ], $args );
		} else {
			throw new InvalidActionException( 'There is no such action.' );
		}
	}

	public function debug_wp_query_clauses( $clauses, $query ) {
		global $fibofilters_troubleshooting_wp_query_clauses;

		if ( $query->get( 'fibofilters_log_posts_clauses' ) === '1' ) {
			$fibofilters_troubleshooting_wp_query_clauses = $clauses;
		}

		return $clauses;
	}

	private function action_reset_async_test_results() {
		delete_transient( self::TRANSIENT_RESULTS_KEY );

		return [
			'message' => _x( 'Done.', '[admin]', 'fibofilters' ),
			'reload'  => true,
		];
	}

	private function action_maybe_reset_sources_analyse_data() {
		$tests_results = get_transient( self::TRANSIENT_RESULTS_KEY );

		if ( empty( $tests_results['invalid_filter_configuration'] ) ) {
			return [];
		}

		$status = $tests_results['invalid_filter_configuration']['status'] ?? null;

		if ( $status === 'critical' ) {
			delete_transient( Env::$plugin_prefix . 'sources_analyse_data' );
		}

		return [];
	}

	private function action_dismiss_test( array $args ): array {
		$test = ! empty( $args['test'] ) ? $args['test'] : '';
		if ( empty( $test ) ) {
			return [
				'message' => _x( 'Invalid ID', '[admin]', 'fibofilters' ),
				'reload'  => false,
			];
		}

		$dismissed_tests   = $this->get_dismissed_tests();
		$dismissed_tests[] = $test;

		update_option( self::DISMISSED_TESTS_OPTION_KEY, array_unique( $dismissed_tests ), false );

		return [
			'message' => _x( 'Done.', '[admin]', 'fibofilters' ),
			'reload'  => true,
		];
	}

	/**
	 * Run test directly
	 *
	 * @param callable $callback Method to run.
	 *
	 * @return mixed|void
	 */
	private function perform_test( callable $callback ) {
		return apply_filters( 'fibofilters/troubleshooting/test_result', call_user_func( $callback ) );
	}

	/**
	 * Get result of async test
	 *
	 * @param string $test Test name.
	 */
	private function get_result( $test ): array {
		$async_tests_results = get_transient( self::TRANSIENT_RESULTS_KEY );

		return $async_tests_results[ $test ] ?? [];
	}

	/**
	 * Storing result of async test
	 *
	 * Direct tests do not need to be saved.
	 *
	 * @param array $result Tes result.
	 */
	private function store_result( $result ) {
		$async_tests_results = get_transient( self::TRANSIENT_RESULTS_KEY );
		if ( ! is_array( $async_tests_results ) ) {
			$async_tests_results = [];
		}
		$async_tests_results[ $result['test'] ] = $result;
		set_transient( self::TRANSIENT_RESULTS_KEY, $async_tests_results, 15 * 60 );
	}

	/**
	 * Get tests marked as dismissed
	 *
	 * @return array
	 */
	private function get_dismissed_tests(): array {
		return get_option( self::DISMISSED_TESTS_OPTION_KEY, [] );
	}
}
