<?php
/**
 * Class Compatibility
 *
 * @package WooCommerce\Payments\Compatibility
 */

namespace WCPay\MultiCurrency;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Utilities\OrderUtil;
use WC_Order;
use WC_Order_Refund;
use WCPay\MultiCurrency\Interfaces\MultiCurrencySettingsInterface;

defined( 'ABSPATH' ) || exit;

/**
 * Class that contains Multi-Currency related support for WooCommerce analytics.
 */
class Analytics {
	const PRIORITY_EARLY   = 1;
	const PRIORITY_DEFAULT = 10;
	const PRIORITY_LATE    = 20;
	const PRIORITY_LATEST  = 99999;
	const SCRIPT_NAME      = 'WCPAY_MULTI_CURRENCY_ANALYTICS';

	const SUPPORTED_CONTEXTS = [ 'orders', 'products', 'variations', 'categories', 'coupons', 'taxes' ];

	/**
	 * SQL string replacements made by the analytics Multi-Currency extension.
	 *
	 * @var array
	 */
	protected $sql_replacements = [];

	/**
	 * Instance of MultiCurrency.
	 *
	 * @var MultiCurrency $multi_currency
	 */
	private $multi_currency;

	/**
	 * Instance of MultiCurrencySettingsInterface.
	 *
	 * @var MultiCurrencySettingsInterface $settings_service
	 */
	private $settings_service;

	/**
	 * Constructor
	 *
	 * @param MultiCurrency                  $multi_currency   Instance of MultiCurrency.
	 * @param MultiCurrencySettingsInterface $settings_service Instance of MultiCurrencySettingsInterface.
	 */
	public function __construct( MultiCurrency $multi_currency, MultiCurrencySettingsInterface $settings_service ) {
		$this->multi_currency   = $multi_currency;
		$this->settings_service = $settings_service;
		$this->init();
	}

	/**
	 * Initialise all actions, filters and hooks related to analytics support.
	 *
	 * @return void
	 */
	public function init() {
		if ( is_admin() && current_user_can( 'manage_woocommerce' ) ) {
			add_filter( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] );
			$this->register_customer_currencies();
		}

		if ( $this->settings_service->is_dev_mode() ) {
			add_filter( 'woocommerce_analytics_report_should_use_cache', [ $this, 'disable_report_caching' ] );
		}

		// Add a filter when the order stats table is updated.
		add_filter( 'woocommerce_analytics_update_order_stats_data', [ $this, 'update_order_stats_data' ], self::PRIORITY_LATEST, 2 );

		// Add filters when the query args are updated.
		add_filter( 'woocommerce_analytics_orders_query_args', [ $this, 'apply_customer_currency_args' ] );
		add_filter( 'woocommerce_analytics_orders_stats_query_args', [ $this, 'apply_customer_currency_args' ] );

		// If we aren't making a REST request, or no multi currency orders exist in the merchant's store,
		// return before adding these filters.

		if ( ! WC()->is_rest_api_request() || ! $this->has_multi_currency_orders() ) {
			return;
		}

		$this->set_sql_replacements();

		// Add the filters that are applied in each analytics query.
		add_filter( 'woocommerce_analytics_clauses_select', [ $this, 'filter_select_clauses' ], self::PRIORITY_LATE, 2 );
		add_filter( 'woocommerce_analytics_clauses_join', [ $this, 'filter_join_clauses' ], self::PRIORITY_LATE, 2 );

		// Add the WHERE clause filter which is applied only on Order related queries.
		add_filter( 'woocommerce_analytics_clauses_where_orders_subquery', [ $this, 'filter_where_clauses' ] );
		add_filter( 'woocommerce_analytics_clauses_where_orders_stats_total', [ $this, 'filter_where_clauses' ] );
		add_filter( 'woocommerce_analytics_clauses_where_orders_stats_interval', [ $this, 'filter_where_clauses' ] );

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if ( ! empty( $_GET['currency'] ) && $_GET['currency'] !== $this->multi_currency->get_default_currency()->get_code() ) {
			add_filter( 'woocommerce_analytics_clauses_select_orders_subquery', [ $this, 'filter_select_orders_clauses' ] );
			add_filter( 'woocommerce_analytics_clauses_select_orders_stats_total', [ $this, 'filter_select_orders_clauses' ] );
		}
	}

	/**
	 * Register the CSS and JS scripts.
	 *
	 * @return void
	 */
	public function register_admin_scripts() {
		$this->multi_currency->register_script_with_dependencies( self::SCRIPT_NAME, 'dist/multi-currency-analytics' );
	}

	/**
	 * Add the list of currencies used on the store to the wcSettings to allow it to be accessed by the front-end JS script.
	 *
	 * @return void
	 */
	public function register_customer_currencies() {
		$data_registry = Package::container()->get( AssetDataRegistry::class );
		if ( $data_registry->exists( 'customerCurrencies' ) ) {
			return;
		}

		$currencies           = $this->multi_currency->get_all_customer_currencies();
		$available_currencies = $this->multi_currency->get_available_currencies();
		$currency_options     = [];

		$default_currency = $this->multi_currency->get_default_currency();

		// Add default currency to the list if it does not exist.
		if ( ! in_array( $default_currency->get_code(), $currencies, true ) ) {
			$currencies[] = $default_currency->get_code();
		}

		foreach ( $currencies as $currency ) {
			if ( ! isset( $available_currencies[ $currency ] ) ) {
				continue;
			}

			$currency_details   = $available_currencies[ $currency ];
			$currency_options[] = [
				'label' => html_entity_decode( $currency_details->get_name() ),
				'value' => $currency_details->get_code(),
			];
		}

		$data_registry->add( 'customerCurrencies', $currency_options );
	}

	/**
	 * Enqueue scripts used on the analytics WP Admin pages.
	 *
	 * @return void
	 */
	public function enqueue_admin_scripts() {
		$this->register_admin_scripts();

		wp_enqueue_script( self::SCRIPT_NAME );
	}

	/**
	 * Disables report caching. Used for development of analytics related functionality.
	 * To disable report caching
	 *
	 * @param array $args Filter arguments.
	 *
	 * @return boolean
	 */
	public function disable_report_caching( $args ): bool {
		return false;
	}

	/**
	 * If the customer currency is set, add it as a query parameter to requests to the data store.
	 * This ensures that the cache is updated when this value is changed between requests.
	 *
	 * @param array $args The arguments passed in via the GET request.
	 *
	 * @return array
	 */
	public function apply_customer_currency_args( $args ): array {
		$currency_args = $this->get_customer_currency_args_from_request();
		return array_merge( $args, $currency_args );
	}

	/**
	 * When an order is updated in the stats table, perform a check to see if it is a Multi-Currency order
	 * and convert the information into the store's default currency if it is.
	 *
	 * @param array    $args  - An array of the arguments to be inserted into the order stats table.
	 * @param WC_Order $order - The order itself.
	 *
	 * @return array
	 */
	public function update_order_stats_data( array $args, $order ): array {
		if ( ! $this->should_convert_order_stats( $order ) ) {
			return $args;
		}

		$stripe_exchange_rate = $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate', true )
			? (float) $order->get_meta( '_wcpay_multi_currency_stripe_exchange_rate', true )
			: null;
		$order_exchange_rate  = ( 1 / (float) $order->get_meta( '_wcpay_multi_currency_order_exchange_rate', true ) );

		$exchange_rate = $stripe_exchange_rate ?? $order_exchange_rate;

		$dp                     = wc_get_price_decimals();
		$args['net_total']      = round( $this->convert_amount( (float) $args['net_total'], $exchange_rate ), $dp );
		$args['shipping_total'] = round( $this->convert_amount( (float) $args['shipping_total'], $exchange_rate ), $dp );
		$args['tax_total']      = round( $this->convert_amount( (float) $args['tax_total'], $exchange_rate ), $dp );
		$args['total_sales']    = $args['net_total'] + $args['shipping_total'] + $args['tax_total'];

		return $args;
	}

	/**
	 * Add columns to get the currency and converted amount (if required).
	 *
	 * @param string[] $clauses - An array containing the SELECT clauses to be applied.
	 * @param string   $context - The context in which this SELECT clause is being called.
	 *
	 * @return array
	 */
	public function filter_select_clauses( array $clauses, $context ): array {
		// If we are unable to identify a context, just return the clauses as is.
		if ( is_null( $context ) ) {
			return $clauses;
		}

		if ( apply_filters( MultiCurrency::FILTER_PREFIX . 'disable_filter_select_clauses', false ) ) {
			return $clauses;
		}

		$context_parts = explode( '_', $context );
		$context_page  = $context_parts[0] ?? 'generic';
		$context_type  = $context_parts[1] ?? null;

		// If we can't identify the type of context we are running in (stats or subquery), then return the clauses as is.
		if ( ! in_array( $context_type, [ 'stats', 'subquery' ], true ) ) {
			return $clauses;
		}

		$new_clauses      = [];
		$sql_replacements = $this->get_sql_replacements();

		foreach ( $clauses as $clause ) {
			if ( ! array_key_exists( $context_page, $sql_replacements ) ) {
				$replacements_array = $sql_replacements['generic'] ?? [];
			} else {
				$replacements_array = $sql_replacements[ $context_page ] ?? [];
			}

			foreach ( $replacements_array as $find => $replace ) {
				if ( strpos( $clause, $find ) !== false ) {
					$clause = str_replace(
						$find,
						$replace,
						$clause
					);
				}
			}

			$new_clauses[] = $clause;
		}

		if ( $this->is_supported_context( $context ) && ( in_array( $context_page, self::SUPPORTED_CONTEXTS, true ) || $this->is_order_stats_table_used_in_clauses( $clauses ) ) ) {
			if ( $this->is_cot_enabled() ) {
				$new_clauses[] = ', wcpay_multicurrency_order_currency.currency AS order_currency';
			} else {
				$new_clauses[] = ', wcpay_multicurrency_currency_meta.meta_value AS order_currency';
			}
			$new_clauses[] = ', wcpay_multicurrency_default_currency_meta.meta_value AS order_default_currency';
			$new_clauses[] = ', wcpay_multicurrency_exchange_rate_meta.meta_value AS exchange_rate';
			$new_clauses[] = ', wcpay_multicurrency_stripe_exchange_rate_meta.meta_value AS stripe_exchange_rate';
		}

		return apply_filters( MultiCurrency::FILTER_PREFIX . 'filter_select_clauses', $new_clauses );
	}

	/**
	 * Add a JOIN so that we can get the currency information.
	 *
	 * @param string[] $clauses - An array containing the JOIN clauses to be applied.
	 * @param string   $context - The context in which this SELECT clause is being called.
	 *
	 * @return array
	 */
	public function filter_join_clauses( array $clauses, $context ): array {
		global $wpdb;

		if ( apply_filters( MultiCurrency::FILTER_PREFIX . 'disable_filter_join_clauses', false ) ) {
			return $clauses;
		}

		$context_parts = explode( '_', $context, 2 );
		$context_page  = $context_parts[0] ?? 'generic';

		$prefix                   = 'wcpay_multicurrency_';
		$currency_tbl             = $prefix . 'currency_meta';
		$default_currency_tbl     = $prefix . 'default_currency_meta';
		$exchange_rate_tbl        = $prefix . 'exchange_rate_meta';
		$stripe_exchange_rate_tbl = $prefix . 'stripe_exchange_rate_meta';

		// Allow this to work with custom order tables as well.
		if ( $this->is_cot_enabled() ) {
			$meta_table = $wpdb->prefix . 'wc_orders_meta';
			$id_field   = 'order_id';

			$currency_tbl = $prefix . 'order_currency';
		} else {
			$meta_table = $wpdb->postmeta;
			$id_field   = 'post_id';
		}

		// If this is a supported context, add the joins. If this is an unsupported context, see if we can add the joins.
		if ( $this->is_supported_context( $context ) && ( in_array( $context_page, self::SUPPORTED_CONTEXTS, true ) || $this->is_order_stats_table_used_in_clauses( $clauses ) ) ) {
			if ( $this->is_cot_enabled() ) {
				$clauses[] = "LEFT JOIN {$wpdb->prefix}wc_orders {$currency_tbl} ON {$wpdb->prefix}wc_order_stats.order_id = {$currency_tbl}.id";
			} else {
				$clauses[] = "LEFT JOIN {$meta_table} {$currency_tbl} ON {$wpdb->prefix}wc_order_stats.order_id = {$currency_tbl}.{$id_field} AND {$currency_tbl}.meta_key = '_order_currency'";

			}
			$clauses[] = "LEFT JOIN {$meta_table} {$default_currency_tbl} ON {$wpdb->prefix}wc_order_stats.order_id = {$default_currency_tbl}.{$id_field} AND {$default_currency_tbl}.meta_key = '_wcpay_multi_currency_order_default_currency'";
			$clauses[] = "LEFT JOIN {$meta_table} {$exchange_rate_tbl} ON {$wpdb->prefix}wc_order_stats.order_id = {$exchange_rate_tbl}.{$id_field} AND {$exchange_rate_tbl}.meta_key = '_wcpay_multi_currency_order_exchange_rate'";
			$clauses[] = "LEFT JOIN {$meta_table} {$stripe_exchange_rate_tbl} ON {$wpdb->prefix}wc_order_stats.order_id = {$stripe_exchange_rate_tbl}.{$id_field} AND {$stripe_exchange_rate_tbl}.meta_key = '_wcpay_multi_currency_stripe_exchange_rate'";
		}

		return apply_filters( MultiCurrency::FILTER_PREFIX . 'filter_join_clauses', $clauses );
	}

	/**
	 * Add the WHERE clauses (if a customer currency has been selected).
	 *
	 * @param string[] $clauses - An array containing the JOIN clauses to be applied.
	 *
	 * @return array
	 */
	public function filter_where_clauses( array $clauses ): array {
		if ( apply_filters( MultiCurrency::FILTER_PREFIX . 'disable_filter_where_clauses', false ) ) {
			return $clauses;
		}

		$prefix = 'wcpay_multicurrency_';
		if ( $this->is_cot_enabled() ) {
			$currency_field = $prefix . 'order_currency.currency';
		} else {
			$currency_field = $prefix . 'currency_meta.meta_value';
		}

		$currency_args = $this->get_customer_currency_args_from_request();
		if ( ! empty( $currency_args['currency_is'] ) ) {
			$currency_is = sprintf( "'%s'", implode( "', '", array_map( 'esc_sql', $currency_args['currency_is'] ) ) );
			$clauses[]   = "AND {$currency_field} IN ({$currency_is})";
		}

		if ( ! empty( $currency_args['currency_is_not'] ) ) {
			$currency_is_not = sprintf( "'%s'", implode( "', '", array_map( 'esc_sql', $currency_args['currency_is_not'] ) ) );
			$clauses[]       = "AND {$currency_field} NOT IN ({$currency_is_not})";
		}

		if ( ! empty( $currency_args['currency'] ) ) {
			global $wpdb;
			$expression = "AND {$currency_field} = '%s'";
			$clauses[]  = $wpdb->prepare( $expression, $currency_args['currency'] ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		}

		return apply_filters( MultiCurrency::FILTER_PREFIX . 'filter_where_clauses', $clauses );
	}

	/**
	 * Convert amounts back to order currency (if a currency has been selected).
	 * Skipping it for default currency.
	 *
	 * @param string[] $clauses - An array containing the SELECT orders clauses to be applied.
	 * @return array
	 */
	public function filter_select_orders_clauses( array $clauses ): array {
		if ( apply_filters( MultiCurrency::FILTER_PREFIX . 'disable_filter_select_orders_clauses', false ) ) {
			return $clauses;
		}

		global $wpdb;
		$exchange_rate        = 'wcpay_multicurrency_exchange_rate_meta.meta_value';
		$stripe_exchange_rate = 'wcpay_multicurrency_stripe_exchange_rate_meta.meta_value';
		$net_total            = "{$wpdb->prefix}wc_order_stats.net_total";

		foreach ( $clauses as $k => $clause ) {
			if ( strpos( $clause, $net_total ) !== false ) {
				$is_orders_subquery = strpos( $clause, $net_total . ',' ) !== false;
				$variable           = $is_orders_subquery ? "$net_total," : $net_total;
				$alias              = $is_orders_subquery ? ' as net_total,' : '';
				$dp                 = wc_get_price_decimals();

				$clauses[ $k ] = str_replace(
					$variable,
					$this->generate_case_when(
						$stripe_exchange_rate,
						"ROUND($net_total / $stripe_exchange_rate, $dp)",
						"ROUND($net_total * $exchange_rate, $dp)"
					) . $alias,
					$clause
				);
			}
		}

		return apply_filters( MultiCurrency::FILTER_PREFIX . 'filter_select_orders_clauses', $clauses );
	}

	/**
	 * Check to see whether we should convert an order to store in the order stats table.
	 *
	 * @param WC_Order|WC_Order_Refund $order The order.
	 *
	 * @return boolean
	 */
	private function should_convert_order_stats( $order ): bool {
		$default_currency = $this->multi_currency->get_default_currency();

		// If this order was in the default currency, or the meta information isn't set on the order, return false.
		if ( ! $order ||
			$order->get_currency() === $default_currency->get_code() ||
			! $order->get_meta( '_wcpay_multi_currency_order_exchange_rate', true ) ||
			$order->get_meta( '_wcpay_multi_currency_order_default_currency', true ) !== $default_currency->get_code()
		) {
			return false;
		}

		return true;
	}

	/**
	 * Convert an amount to the store's default currency in order to store in the stats table.
	 *
	 * @param float $amount The amount to convert into the store's default currency.
	 * @param float $exchange_rate The exchange rate to use for the conversion.
	 *
	 * @return float The converted amount.
	 */
	private function convert_amount( float $amount, float $exchange_rate ): float {
		return $amount * $exchange_rate;
	}

	/**
	 * Check whether the order stats table is referenced in the clauses, to work out whether
	 * to add the JOIN columns for Multi-Currency.
	 *
	 * @param array $clauses The array containing the clauses used.
	 *
	 * @return boolean Whether the order stats table is referenced.
	 */
	private function is_order_stats_table_used_in_clauses( array $clauses ): bool {
		global $wpdb;

		foreach ( $clauses as $clause ) {
			if ( strpos( $clause, "{$wpdb->prefix}wc_order_stats" ) !== false ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * There are some queries which are made in the analytics which are actually sub-queries
	 * which are used to join on an individual item/coupon/tax code. In these cases, rather than
	 * the context being the expected format e.g. product_stats_total, it will simply be 'product'.
	 * In these cases, we don't want to add the join columns or select them.
	 *
	 * @param string $context The context the query was made in.
	 *
	 * @return boolean
	 */
	private function is_supported_context( string $context ): bool {
		$unsupported_contexts = [ 'products', 'coupons', 'taxes', 'variations', 'categories' ];

		if ( in_array( $context, $unsupported_contexts, true ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Generate a case when statement using the provided variables.
	 *
	 * @param string $variable The SQL variable we want to check for NULL.
	 * @param string $then     The THEN clause.
	 * @param string $else     The ELSE clause.
	 *
	 * @return string
	 */
	private function generate_case_when( string $variable, string $then, string $else ): string {
		return "CASE WHEN {$variable} IS NOT NULL THEN {$then} ELSE {$else} END";
	}

	/**
	 * Perform an SQL query to determine whether Multi Currency has ever been used on this store,
	 * by checking how many orders are in the database where an exchange currency rate has been stored.
	 *
	 * @return bool
	 */
	private function has_multi_currency_orders() {
		global $wpdb;

		// Using full SQL instead of variables to keep WPCS happy.
		if ( $this->is_cot_enabled() ) {
			$result = $wpdb->get_var(
				"SELECT EXISTS(
					SELECT 1
					FROM {$wpdb->prefix}wc_orders_meta
					WHERE meta_key = '_wcpay_multi_currency_order_exchange_rate'
					LIMIT 1)
				AS count;"
			);
		} else {
			$result = $wpdb->get_var(
				"SELECT EXISTS(
					SELECT 1
					FROM {$wpdb->postmeta}
					WHERE meta_key = '_wcpay_multi_currency_order_exchange_rate'
					LIMIT 1)
				AS count;"
			);
		}

		return intval( $result ) === 1;
	}

	/**
	 * Get the SQL replacements variable.
	 *
	 * @return array
	 */
	private function get_sql_replacements(): array {
		return $this->sql_replacements;
	}

	/**
	 * Check the passed in query params to see if currency has been passed in.
	 * Will return null if no currency variable was passed in, otherwise will
	 * return the currency.
	 *
	 * @return array
	 */
	private function get_customer_currency_args_from_request(): array {
		$args = [
			'currency_is'     => [],
			'currency_is_not' => [],
			'currency'        => null,
		];

		/* phpcs:disable WordPress.Security.NonceVerification */
		if ( isset( $_GET['currency_is'] ) && is_array( $_GET['currency_is'] ) ) {
			$args['currency_is'] = array_map( 'sanitize_text_field', wp_unslash( $_GET['currency_is'] ) );
		}

		if ( isset( $_GET['currency_is_not'] ) ) {
			$args['currency_is_not'] = array_map( 'sanitize_text_field', wp_unslash( $_GET['currency_is_not'] ) );
		}

		if ( isset( $_GET['currency'] ) ) {
			$args['currency'] = sanitize_text_field( wp_unslash( $_GET['currency'] ) );
		}
		/* phpcs:enable WordPress.Security.NonceVerification */

		return $args;
	}

	/**
	 * Set the SQL replacements variable.
	 *
	 * @return void
	 */
	private function set_sql_replacements() {
		$default_currency     = 'wcpay_multicurrency_default_currency_meta.meta_value';
		$exchange_rate        = 'wcpay_multicurrency_exchange_rate_meta.meta_value';
		$stripe_exchange_rate = 'wcpay_multicurrency_stripe_exchange_rate_meta.meta_value';

		$discount_amount       = $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(discount_amount * {$stripe_exchange_rate}, 2)", "ROUND(discount_amount * (1 / {$exchange_rate} ), 2)" ), 'discount_amount' );
		$product_net_revenue   = $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(product_net_revenue * {$stripe_exchange_rate}, 2)", "ROUND(product_net_revenue * (1 / {$exchange_rate} ), 2)" ), 'product_net_revenue' );
		$product_gross_revenue = $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(product_gross_revenue * {$stripe_exchange_rate}, 2)", "ROUND(product_gross_revenue * (1 / {$exchange_rate} ), 2)" ), 'product_gross_revenue' );

		$this->sql_replacements = [
			'generic'    => [
				'discount_amount'       => $discount_amount,
				'product_net_revenue'   => $product_net_revenue,
				'product_gross_revenue' => $product_gross_revenue,
			],
			'orders'     => [
				'discount_amount' => $discount_amount,
			],
			'products'   => [
				'product_net_revenue'   => $product_net_revenue,
				'product_gross_revenue' => $product_gross_revenue,
			],
			'variations' => [
				'product_net_revenue'   => $product_net_revenue,
				'product_gross_revenue' => $product_gross_revenue,
			],
			'categories' => [
				'product_net_revenue'   => $product_net_revenue,
				'product_gross_revenue' => $product_gross_revenue,
			],
			'taxes'      => [
				'SUM(total_tax)'    => 'SUM(' . $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(total_tax * {$stripe_exchange_rate}, 2)", "ROUND(total_tax * (1 / {$exchange_rate} ), 2)" ), 'total_tax' ) . ')',
				'SUM(order_tax)'    => 'SUM(' . $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(order_tax * {$stripe_exchange_rate}, 2)", "ROUND(order_tax * (1 / {$exchange_rate} ), 2)" ), 'order_tax' ) . ')',
				'SUM(shipping_tax)' => 'SUM(' . $this->generate_case_when( $default_currency, $this->generate_case_when( $stripe_exchange_rate, "ROUND(shipping_tax * {$stripe_exchange_rate}, 2)", "ROUND(shipping_tax * (1 / {$exchange_rate} ), 2)" ), 'shipping_tax' ) . ')',
			],
			'coupons'    => [
				'discount_amount' => $discount_amount,
			],
		];
	}

	/**
	 * Checks whether Custom Order Tables are enabled.
	 *
	 * @return bool
	 */
	private function is_cot_enabled(): bool {
		return class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled();
	}
}
