<?php

/**
 * Registers and configures the necessary Javascript for the button, credit messaging and DCC fields.
 *
 * @package WooCommerce\PayPalCommerce\Button\Assets
 */
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Assets;

use Exception;
use WC_Cart;
use WC_Order;
use WC_Payment_Tokens;
use WC_Product;
use WC_Product_Variable;
use WC_Product_Variation;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
use WooCommerce\PayPalCommerce\Assets\AssetGetter;
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\GetOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\Context;
use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule;
use WooCommerce\PayPalCommerce\PayLaterWCBlocks\PayLaterWCBlocksModule;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest;
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsProvider;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
 * Class SmartButton
 */
class SmartButton implements \WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface
{
    use FreeTrialHandlerTrait;
    /**
     * The Settings status helper.
     *
     * @var SettingsStatus
     */
    protected $settings_status;
    /**
     * Context helper: what page is it, is this a continuation mode, etc.
     *
     * @var Context
     */
    protected Context $context;
    private AssetGetter $asset_getter;
    /**
     * The assets version.
     *
     * @var string
     */
    private $version;
    private SettingsProvider $settings_provider;
    /**
     * The Payer Factory.
     *
     * @var PayerFactory
     */
    private $payer_factory;
    /**
     * The client ID.
     *
     * @var string
     */
    private $client_id;
    /**
     * The Request Data.
     *
     * @var RequestData
     */
    private $request_data;
    /**
     * The DCC Applies helper.
     *
     * @var DccApplies
     */
    private $dcc_applies;
    /**
     * The Subscription Helper.
     *
     * @var SubscriptionHelper
     */
    private $subscription_helper;
    /** @var callable */
    private $get_subscriptions_mode;
    /**
     * The Messages apply helper.
     *
     * @var MessagesApply
     */
    private $messages_apply;
    /**
     * The environment object.
     *
     * @var Environment
     */
    private $environment;
    /**
     * The getter of the 3-letter currency code of the shop.
     *
     * @var CurrencyGetter
     */
    private CurrencyGetter $currency;
    /**
     * Whether the basic JS validation of the form iss enabled.
     *
     * @var bool
     */
    private $basic_checkout_validation_enabled;
    /**
     * Whether to execute WC validation of the checkout form.
     *
     * @var bool
     */
    protected $early_validation_enabled;
    /**
     * The contexts that should have the Pay Now button.
     *
     * @var string[]
     */
    private $pay_now_contexts;
    /**
     * The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
     *
     * @var string[]
     */
    private $funding_sources_without_redirect;
    /**
     * Session handler.
     *
     * @var SessionHandler
     */
    private $session_handler;
    /**
     * Whether the shipping should be handled in PayPal.
     *
     * @var bool
     */
    private $should_handle_shipping_in_paypal;
    /**
     * List of funding sources to be disabled.
     *
     * @var DisabledFundingSources
     */
    private $disabled_funding_sources;
    /**
     * Provides details about the DCC configuration.
     *
     * @var CardPaymentsConfiguration
     */
    private CardPaymentsConfiguration $dcc_configuration;
    /**
     * The PayPal Partner Attribution Helper.
     *
     * @var PartnerAttribution
     */
    protected PartnerAttribution $partner_attribution;
    /**
     * Whether the server-side shipping callback is enabled (feature flag).
     */
    private bool $server_side_shipping_callback_enabled;
    /**
     * Whether the AppSwitch is enabled (feature flag).
     */
    private bool $appswitch_enabled;
    /**
     * Whether the final review is enabled in blocks settings.
     */
    private bool $final_review_enabled;
    public function __construct(AssetGetter $asset_getter, string $version, SessionHandler $session_handler, SettingsProvider $settings_provider, PayerFactory $payer_factory, string $client_id, RequestData $request_data, DccApplies $dcc_applies, SubscriptionHelper $subscription_helper, callable $get_subscriptions_mode, MessagesApply $messages_apply, Environment $environment, SettingsStatus $settings_status, CurrencyGetter $currency, bool $basic_checkout_validation_enabled, bool $early_validation_enabled, array $pay_now_contexts, array $funding_sources_without_redirect, bool $should_handle_shipping_in_paypal, bool $server_side_shipping_callback_enabled, bool $appswitch_enabled, DisabledFundingSources $disabled_funding_sources, CardPaymentsConfiguration $dcc_configuration, PartnerAttribution $partner_attribution, bool $final_review_enabled, Context $context)
    {
        $this->asset_getter = $asset_getter;
        $this->version = $version;
        $this->session_handler = $session_handler;
        $this->settings_provider = $settings_provider;
        $this->payer_factory = $payer_factory;
        $this->client_id = $client_id;
        $this->request_data = $request_data;
        $this->dcc_applies = $dcc_applies;
        $this->subscription_helper = $subscription_helper;
        $this->get_subscriptions_mode = $get_subscriptions_mode;
        $this->messages_apply = $messages_apply;
        $this->environment = $environment;
        $this->settings_status = $settings_status;
        $this->currency = $currency;
        $this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
        $this->early_validation_enabled = $early_validation_enabled;
        $this->pay_now_contexts = $pay_now_contexts;
        $this->funding_sources_without_redirect = $funding_sources_without_redirect;
        $this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal;
        $this->server_side_shipping_callback_enabled = $server_side_shipping_callback_enabled;
        $this->appswitch_enabled = $appswitch_enabled;
        $this->disabled_funding_sources = $disabled_funding_sources;
        $this->dcc_configuration = $dcc_configuration;
        $this->partner_attribution = $partner_attribution;
        $this->final_review_enabled = $final_review_enabled;
        $this->context = $context;
    }
    /**
     * Registers the necessary action hooks to render the HTML depending on the settings.
     *
     * @return bool
     */
    public function render_wrapper(): bool
    {
        $this->context->init_context();
        if ($this->settings_provider->gateway_enabled(PayPalGateway::ID)) {
            $this->render_button_wrapper_registrar();
            $this->render_message_wrapper_registrar();
        }
        if ($this->dcc_configuration->is_enabled()) {
            $this->render_dcc_wrapper();
        }
        do_action('woocommerce_paypal_payments_smart_button_render_wrapper');
        $this->sanitize_woocommerce_filters();
        return \true;
    }
    /**
     * Registers hooks and callbacks that are only relevant for DCC (ACDC) payments.
     *
     * @return void
     */
    private function render_dcc_wrapper(): void
    {
        add_action($this->checkout_dcc_button_renderer_hook(), array($this, 'dcc_renderer'), 11);
        add_action($this->pay_order_renderer_hook(), array($this, 'dcc_renderer'), 11);
        $subscription_helper = $this->subscription_helper;
        add_filter('woocommerce_credit_card_form_fields', function (array $default_fields, $id) use ($subscription_helper): array {
            if (CreditCardGateway::ID === $id && is_user_logged_in() && $this->settings_provider->save_card_details() && apply_filters('woocommerce_paypal_payments_should_render_card_custom_fields', \true)) {
                $default_fields['card-vault'] = sprintf('<p class="form-row form-row-wide"><label for="ppcp-credit-card-vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>', esc_html__('Save your Credit Card', 'woocommerce-paypal-payments'));
                if ($subscription_helper->cart_contains_subscription() || $subscription_helper->order_pay_contains_subscription()) {
                    $default_fields['card-vault'] = '';
                }
            }
            return $default_fields;
        }, 10, 2);
    }
    /**
     * Registers the hooks to render the credit messaging HTML depending on the settings.
     *
     * @return bool
     */
    private function render_message_wrapper_registrar(): bool
    {
        if (!apply_filters('woocommerce_paypal_payments_should_render_pay_later_messaging', \true)) {
            return \false;
        }
        if (!$this->settings_status->is_pay_later_messaging_enabled() || !$this->settings_status->has_pay_later_messaging_locations()) {
            return \false;
        }
        $location = $this->context->location();
        if (!$this->settings_status->is_pay_later_messaging_enabled_for_location($location)) {
            return \false;
        }
        $default_pay_order_hook = 'woocommerce_pay_order_before_submit';
        /**
         * The filter returning if the current theme is a block theme or not.
         */
        $is_block_theme = (bool) apply_filters('woocommerce_paypal_payments_messages_renderer_is_block', wp_is_block_theme());
        $has_paylater_block = PayLaterBlockModule::is_block_enabled($this->settings_status) && has_block('woocommerce-paypal-payments/paylater-messages') || PayLaterWCBlocksModule::is_block_enabled($this->settings_status, $location) && (has_block('woocommerce-paypal-payments/checkout-paylater-messages') || has_block('woocommerce-paypal-payments/cart-paylater-messages'));
        $get_hook = function (string $location) use ($default_pay_order_hook, $is_block_theme, $has_paylater_block): ?array {
            switch ($location) {
                case 'checkout':
                    return $this->messages_renderer_hook($location, 'woocommerce_review_order_before_payment', 10);
                case 'cart':
                    return $this->messages_renderer_hook($location, $this->proceed_to_checkout_button_renderer_hook(), 19);
                case 'pay-now':
                    return $this->messages_renderer_hook($location, $default_pay_order_hook, 10);
                case 'product':
                    return $this->messages_renderer_hook($location, $this->single_product_renderer_hook(), 30);
                case 'shop':
                    return $is_block_theme ? $this->messages_renderer_block($location, 'core/query-title', 10) : $this->messages_renderer_hook($location, 'woocommerce_archive_description', 10);
                case 'home':
                    return $is_block_theme ? $this->messages_renderer_block($location, 'core/navigation', 10) : $this->messages_renderer_hook($location, 'loop_start', 20);
                default:
                    return $has_paylater_block ? $this->messages_renderer_hook($location, 'ppcp_paylater_message_block', 10) : null;
            }
        };
        $hook = $get_hook($location);
        if (!$hook) {
            return \false;
        }
        if ($hook['blockName'] ?? \false) {
            $this->message_renderer($hook);
        } else {
            add_action($hook['name'], array($this, 'message_renderer'), $hook['priority']);
        }
        // Looks like there are no hooks like woocommerce_review_order_before_payment on the pay for order page, so have to move using JS.
        if ($location === 'pay-now' && $hook['name'] === $default_pay_order_hook && apply_filters('woocommerce_paypal_payments_put_pay_order_messages_before_payment_methods', \true)) {
            add_action('ppcp_after_pay_order_message_wrapper', function () {
                echo '
<script>
document.querySelector("#payment").before(document.querySelector(".ppcp-messages"))
</script>';
            });
        }
        return \true;
    }
    /**
     * Registers the hooks where to render the button HTML according to the settings.
     *
     * @return bool
     */
    private function render_button_wrapper_registrar(): bool
    {
        if ((is_product() || wc_post_content_has_shortcode('product_page')) && $this->settings_status->is_smart_button_enabled_for_location('product') && !$this->is_free_trial_product() && !is_checkout()) {
            add_action($this->single_product_renderer_hook(), function () {
                $product = wc_get_product();
                if ($product instanceof WC_Product && !$this->product_supports_payment($product)) {
                    return;
                }
                $this->button_renderer(PayPalGateway::ID, 'woocommerce_paypal_payments_single_product_button_render');
            }, 31);
        }
        if ($this->settings_status->is_smart_button_enabled_for_location('mini-cart') && !$this->is_free_trial_cart()) {
            add_action($this->mini_cart_button_renderer_hook(), function () {
                if ($this->is_cart_price_total_zero() || $this->is_free_trial_cart()) {
                    return;
                }
                echo '<p class="woocommerce-mini-cart__buttons buttons">';
                echo '<span id="ppc-button-minicart"></span>';
                do_action('woocommerce_paypal_payments_minicart_button_render');
                echo '</p>';
            }, 30);
        }
        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
        if (isset($available_gateways['ppcp-gateway'])) {
            add_action($this->pay_order_renderer_hook(), function (): void {
                $this->button_renderer(PayPalGateway::ID, 'woocommerce_paypal_payments_payorder_button_render');
                $this->button_renderer(CardButtonGateway::ID);
            }, 20);
            add_action($this->checkout_button_renderer_hook(), function (): void {
                $this->button_renderer(PayPalGateway::ID, 'woocommerce_paypal_payments_checkout_button_render');
                $this->button_renderer(CardButtonGateway::ID);
            });
            $enabled_on_cart = $this->settings_status->is_smart_button_enabled_for_location('cart');
            add_action($this->proceed_to_checkout_button_renderer_hook(), function () use ($enabled_on_cart) {
                if (!is_cart() || !$enabled_on_cart || $this->is_free_trial_cart() || $this->is_cart_price_total_zero() || isset(reset(WC()->cart->cart_contents)['subscription_switch'])) {
                    return;
                }
                $this->button_renderer(PayPalGateway::ID, 'woocommerce_paypal_payments_cart_button_render');
            }, 20);
        }
        return \true;
    }
    /**
     * Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
     */
    public function should_load_ppcp_script(): bool
    {
        if (!$this->settings_provider->gateway_enabled(PayPalGateway::ID)) {
            return \false;
        }
        return $this->should_load_buttons() || $this->should_load_messages() || $this->can_render_dcc();
    }
    /**
     * Determines whether the button component should be loaded.
     */
    public function should_load_buttons(): bool
    {
        if (!$this->settings_provider->gateway_enabled(PayPalGateway::ID)) {
            return \false;
        }
        $smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location($this->context->context());
        $smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location('mini-cart');
        switch ($this->context->context()) {
            case 'checkout':
            case 'cart':
            case 'pay-now':
            case 'checkout-block':
            case 'cart-block':
                return $smart_button_enabled_for_current_location;
            case 'product':
                return $smart_button_enabled_for_current_location || $smart_button_enabled_for_mini_cart;
            default:
                return $smart_button_enabled_for_mini_cart || $this->context->is_block_editor();
        }
    }
    /**
     * Determines whether the Pay Later messages component should be loaded.
     */
    public function should_load_messages(): bool
    {
        if (!$this->settings_provider->gateway_enabled(PayPalGateway::ID)) {
            return \false;
        }
        if (!$this->settings_status->is_pay_later_messaging_enabled()) {
            return \false;
        }
        if (!$this->messages_apply->for_country() || $this->is_free_trial_cart()) {
            return \false;
        }
        $location = $this->context->location();
        $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location($location);
        $has_paylater_block = PayLaterBlockModule::is_block_enabled($this->settings_status) && has_block('woocommerce-paypal-payments/paylater-messages');
        if ('cart-block' === $location || 'checkout-block' === $location) {
            return \true;
        }
        switch ($location) {
            case 'checkout':
            case 'cart':
            case 'pay-now':
            case 'product':
            case 'shop':
            case 'home':
                return $messaging_enabled_for_current_location;
            case 'block-editor':
                return \true;
            default:
                return $has_paylater_block;
        }
    }
    /**
     * Whether DCC fields can be rendered.
     */
    public function can_render_dcc(): bool
    {
        return $this->dcc_configuration->is_acdc_enabled() && in_array($this->context->context(), apply_filters('woocommerce_paypal_payments_can_render_dcc_contexts', array('checkout', 'pay-now', 'add-payment-method')), \true);
    }
    /**
     * Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
     */
    public function enqueue(): void
    {
        if ($this->can_render_dcc()) {
            wp_enqueue_style('ppcp-hosted-fields', $this->asset_getter->get_asset_url('hosted-fields.css'), array(), $this->version);
        }
        wp_enqueue_style('gateway', $this->asset_getter->get_asset_url('gateway.css'), array(), $this->version);
        wp_enqueue_script('ppcp-smart-button', $this->asset_getter->get_asset_url('button.js'), array('jquery'), $this->version, \true);
        wp_localize_script('ppcp-smart-button', 'PayPalCommerceGateway', $this->script_data());
    }
    /**
     * Renders the HTML for the buttons.
     *
     * @param string      $gateway_id The gateway ID, like 'ppcp-gateway'.
     * @param string|null $action_name The action name to be called.
     */
    public function button_renderer(string $gateway_id, ?string $action_name = null)
    {
        $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
        if (!isset($available_gateways[$gateway_id])) {
            return;
        }
        // The wrapper is needed for the loading spinner,
        // otherwise jQuery block() prevents buttons rendering.
        echo '<div class="ppc-button-wrapper">';
        $hook_gateway_id = str_replace('-', '_', $gateway_id);
        /**
         * A hook executed after rendering of the opening tag for the PCP wrapper (before the inner wrapper for the buttons).
         *
         * For the PayPal gateway the hook name is ppcp_start_button_wrapper_ppcp_gateway.
         */
        do_action('ppcp_start_button_wrapper_' . $hook_gateway_id);
        echo '<div id="ppc-button-' . esc_attr($gateway_id) . '"></div>';
        /**
         * A hook executed before rendering of the closing tag for the PCP wrapper (before the inner wrapper for the buttons).
         *
         * For the PayPal gateway the hook name is ppcp_end_button_wrapper_ppcp_gateway.
         */
        do_action('ppcp_end_button_wrapper_' . $hook_gateway_id);
        if (null !== $action_name) {
            do_action($action_name);
        }
        echo '</div>';
    }
    /**
     * Renders the HTML for the credit messaging.
     *
     * @param array|null $block_params If it's to be rendered after a block, contains the block params.
     * @return void
     */
    public function message_renderer($block_params = array()): void
    {
        $product = wc_get_product();
        $location = $this->context->location();
        $location_hook = $this->location_to_hook($location);
        if ($location === 'product' && $product instanceof WC_Product && !$this->product_supports_payment($product)) {
            return;
        }
        /**
         * A hook executed before rendering of the PCP Pay Later messages wrapper.
         */
        do_action("ppcp_before_{$location_hook}_message_wrapper");
        $messages_placeholder = '<div class="ppcp-messages" data-partner-attribution-id="' . esc_attr($this->partner_attribution->get_bn_code()) . '"></div>';
        if (is_array($block_params) && ($block_params['blockName'] ?? \false)) {
            $this->render_after_block($block_params['blockName'], '<div class="wp-block-group alignwide">' . $messages_placeholder . '</div>', $block_params['priority'] ?? 10);
        } else {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            echo $messages_placeholder;
        }
        /**
         * A hook executed after rendering of the PCP Pay Later messages wrapper.
         */
        do_action("ppcp_after_{$location_hook}_message_wrapper");
    }
    /**
     * Renders content after a given block.
     *
     * @param string $name The name of the block to render after.
     * @param string $content The content to be rendered.
     * @param int    $priority The 'render_block' hook priority.
     * @return void
     */
    private function render_after_block(string $name, string $content, int $priority = 10): void
    {
        add_filter(
            'render_block',
            /**
             * Adds content after a given block.
             *
             * @param string $block_content The block content.
             * @param array|mixed $block_params The block params.
             * @return string
             *
             * @psalm-suppress MissingClosureParamType
             */
            function ($block_content, $block_params) use ($name, $content) {
                if (is_array($block_params) && ($block_params['blockName'] ?? null) === $name) {
                    $block_content .= $content;
                }
                return $block_content;
            },
            $priority,
            2
        );
    }
    /**
     * The values for the credit messaging.
     *
     * @return array
     */
    private function message_values(): array
    {
        if (!$this->settings_status->is_pay_later_messaging_enabled()) {
            return array();
        }
        $location = $this->context->location();
        switch ($location) {
            case 'checkout':
            case 'checkout-block':
            case 'pay-now':
                $placement = 'payment';
                break;
            case 'cart':
            case 'cart-block':
                $placement = 'cart';
                break;
            case 'product':
                $placement = 'product';
                break;
            case 'shop':
                $placement = 'product-list';
                break;
            case 'home':
                $placement = 'home';
                break;
            default:
                $placement = 'payment';
                break;
        }
        $product = wc_get_product();
        $amount = 0;
        if ($product instanceof WC_Product) {
            $amount = wc_get_price_including_tax($product);
        } elseif (isset(WC()->cart)) {
            $amount = WC()->cart->get_total('raw');
        }
        // Map checkout-block and cart-block message options to checkout and cart options.
        switch ($location) {
            case 'checkout-block':
                $location = 'checkout';
                break;
            case 'cart-block':
                $location = 'cart';
                break;
            default:
                break;
        }
        $messaging_style = $this->settings_provider->pay_later_messaging_style($location);
        return array('wrapper' => '.ppcp-messages', 'is_hidden' => !$this->is_pay_later_filter_enabled_for_location($this->context->context()), 'block' => array('enabled' => PayLaterBlockModule::is_block_enabled($this->settings_status)), 'amount' => $amount, 'placement' => $placement, 'style' => array('layout' => $messaging_style['layout'], 'logo' => array('type' => $messaging_style['logo_type'], 'position' => $messaging_style['logo_position']), 'text' => array('color' => $messaging_style['text_color'], 'size' => $messaging_style['text_size']), 'color' => $messaging_style['flex_color'], 'ratio' => $messaging_style['ratio']));
    }
    /**
     * Renders the HTML for the DCC fields.
     */
    public function dcc_renderer()
    {
        if (!$this->can_render_dcc()) {
            return;
        }
        /**
         * The WC filter returning the WC order button text.
         * phpcs:disable WordPress.WP.I18n.TextDomainMismatch
         */
        $label = 'checkout' === $this->context->context() ? apply_filters('woocommerce_order_button_text', __('Place order', 'woocommerce')) : __('Pay for order', 'woocommerce');
        // phpcs:enable WordPress.WP.I18n.TextDomainMismatch
        printf('<div id="ppcp-hosted-fields" style="display:none;">
						<button id="place_order" type="submit" class="button alt ppcp-dcc-order-button wp-element-button" style="display: none;">%1$s</button>
					</div>
                    <div id="payments-sdk__contingency-lightbox"></div>
                    <style id="ppcp-hide-dcc">.payment_method_ppcp-credit-card-gateway {display:none;}</style>', esc_html($label));
    }
    /**
     * Whether we can store vault tokens or not.
     *
     * @return bool
     */
    public function can_save_vault_token(): bool
    {
        $merchant_data = $this->settings_provider->merchant_data();
        if (!$merchant_data->client_id) {
            return \false;
        }
        if (!$this->settings_provider->save_paypal_and_venmo()) {
            return \false;
        }
        return \true;
    }
    /**
     * Whether we need to initialize the script to enable tokenization for subscriptions or not.
     *
     * @return bool
     */
    private function has_subscriptions(): bool
    {
        if (!$this->subscription_helper->plugin_is_active()) {
            return \false;
        }
        if ($this->subscription_helper->accept_manual_renewals() && $this->paypal_subscriptions_enabled() !== \true) {
            return \false;
        }
        if (is_product()) {
            return $this->subscription_helper->current_product_is_subscription();
        }
        if (is_wc_endpoint_url('order-pay')) {
            return $this->subscription_helper->order_pay_contains_subscription();
        }
        return $this->subscription_helper->cart_contains_subscription();
    }
    /**
     * Whether PayPal subscriptions is enabled or not.
     *
     * @return bool
     */
    private function paypal_subscriptions_enabled(): bool
    {
        return ($this->get_subscriptions_mode)() === 'subscriptions_api';
    }
    /**
     * Retrieves the 3D Secure contingency settings.
     *
     * @return string
     */
    private function get_3ds_contingency(): string
    {
        return $this->return_3ds_contingency($this->settings_provider->three_d_secure_enum());
    }
    /**
     * Processes and returns the 3D Secure contingency.
     *
     * @param string $contingency The ThreeD secure contingency.
     * @return string
     */
    private function return_3ds_contingency(string $contingency): string
    {
        return apply_filters('woocommerce_paypal_payments_three_d_secure_contingency', $contingency);
    }
    /**
     * Whether the current cart contains a product that requires physical shipping.
     *
     * @return bool True, if any cart item requires shipping.
     */
    private function need_shipping(): bool
    {
        /**
         * Cart instance; might be null, esp. in customizer or in Block Editor.
         *
         * @var null|WC_Cart $cart
         */
        $cart = WC()->cart;
        return $cart && $cart->needs_shipping();
    }
    /**
     * The configuration for the smart buttons.
     *
     * @return array
     */
    public function script_data(): array
    {
        $is_free_trial_cart = $this->is_free_trial_cart();
        $is_acdc_enabled = $this->dcc_configuration->is_acdc_enabled();
        $url_params = $this->url_params();
        $current_context = $this->context->context();
        $this->request_data->enqueue_nonce_fix();
        $localize = array('url' => add_query_arg($url_params, 'https://www.paypal.com/sdk/js'), 'url_params' => $url_params, 'script_attributes' => $this->attributes(), 'client_id' => $this->client_id, 'currency' => $this->currency->get(), 'data_client_id' => array('set_attribute' => is_checkout() && $is_acdc_enabled || $this->can_save_vault_token(), 'endpoint' => \WC_AJAX::get_endpoint(DataClientIdEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(DataClientIdEndpoint::nonce()), 'user' => get_current_user_id(), 'has_subscriptions' => $this->has_subscriptions(), 'paypal_subscriptions_enabled' => $this->paypal_subscriptions_enabled()), 'redirect' => wc_get_checkout_url(), 'context' => $current_context, 'ajax' => array('simulate_cart' => array('endpoint' => \WC_AJAX::get_endpoint(SimulateCartEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(SimulateCartEndpoint::nonce())), 'change_cart' => array('endpoint' => \WC_AJAX::get_endpoint(ChangeCartEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(ChangeCartEndpoint::nonce())), 'create_order' => array('endpoint' => \WC_AJAX::get_endpoint(CreateOrderEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(CreateOrderEndpoint::nonce())), 'approve_order' => array('endpoint' => \WC_AJAX::get_endpoint(ApproveOrderEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(ApproveOrderEndpoint::nonce())), 'get_order' => array('endpoint' => \WC_AJAX::get_endpoint(GetOrderEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(GetOrderEndpoint::nonce())), 'approve_subscription' => array('endpoint' => \WC_AJAX::get_endpoint(ApproveSubscriptionEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(ApproveSubscriptionEndpoint::nonce())), 'save_checkout_form' => array('endpoint' => \WC_AJAX::get_endpoint(SaveCheckoutFormEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(SaveCheckoutFormEndpoint::nonce())), 'validate_checkout' => array('endpoint' => \WC_AJAX::get_endpoint(ValidateCheckoutEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(ValidateCheckoutEndpoint::nonce())), 'cart_script_params' => array('endpoint' => \WC_AJAX::get_endpoint(CartScriptParamsEndpoint::ENDPOINT)), 'create_setup_token' => array('endpoint' => \WC_AJAX::get_endpoint(CreateSetupToken::ENDPOINT), 'nonce' => wp_create_nonce(CreateSetupToken::nonce())), 'create_payment_token' => array('endpoint' => \WC_AJAX::get_endpoint(CreatePaymentToken::ENDPOINT), 'nonce' => wp_create_nonce(CreatePaymentToken::nonce())), 'create_payment_token_for_guest' => array('endpoint' => \WC_AJAX::get_endpoint(CreatePaymentTokenForGuest::ENDPOINT), 'nonce' => wp_create_nonce(CreatePaymentTokenForGuest::nonce())), 'update_shipping' => array('endpoint' => \WC_AJAX::get_endpoint(UpdateShippingEndpoint::ENDPOINT), 'nonce' => wp_create_nonce(UpdateShippingEndpoint::nonce())), 'update_customer_shipping' => array('shipping_options' => array('endpoint' => home_url(UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'select-shipping-rate')), 'shipping_address' => array('cart_endpoint' => home_url(UpdateShippingEndpoint::WC_STORE_API_ENDPOINT), 'update_customer_endpoint' => home_url(UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer')), 'wp_rest_nonce' => wp_create_nonce('wc_store_api'), 'update_shipping_method' => \WC_AJAX::get_endpoint('update_shipping_method'))), 'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(), 'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(), 'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(), 'variable_paypal_subscription_variation_from_cart' => $this->subscription_helper->paypal_subscription_variation_from_cart(), 'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(), 'locations_with_subscription_product' => $this->subscription_helper->locations_with_subscription_product(), 'enforce_vault' => $this->has_subscriptions(), 'can_save_vault_token' => $this->can_save_vault_token(), 'is_free_trial_cart' => $is_free_trial_cart, 'bn_codes' => $this->bn_codes(), 'payer' => $this->payerData(), 'button' => array('wrapper' => '#ppc-button-' . PayPalGateway::ID, 'is_disabled' => $this->is_button_disabled(), 'mini_cart_wrapper' => '#ppc-button-minicart', 'is_mini_cart_disabled' => $this->is_button_disabled('mini-cart'), 'cancel_wrapper' => '#ppcp-cancel', 'mini_cart_style' => $this->normalize_style(array('layout' => $this->style_for_context('layout', 'mini-cart'), 'color' => $this->style_for_context('color', 'mini-cart'), 'shape' => $this->style_for_context('shape', 'mini-cart'), 'label' => $this->style_for_context('label', 'mini-cart'), 'tagline' => $this->style_for_context('tagline', 'mini-cart'), 'height' => $this->normalize_height($this->style_for_context('height', 'mini-cart', 35), 25, 55))), 'style' => $this->normalize_style(array('layout' => $this->style_for_context('layout', $current_context), 'color' => $this->style_for_context('color', $current_context), 'shape' => $this->style_for_context('shape', $current_context), 'label' => $this->style_for_context('label', $current_context), 'tagline' => $this->style_for_context('tagline', $current_context), 'height' => in_array($current_context, array('cart-block', 'checkout-block'), \true) ? $this->normalize_height($this->style_for_context('height', $current_context, 48), 40, 55) : null))), 'separate_buttons' => array('card' => array('id' => CardButtonGateway::ID, 'wrapper' => '#ppc-button-' . CardButtonGateway::ID, 'style' => $this->normalize_style(array('shape' => $this->style_for_apm('shape', 'card'), 'color' => $this->style_for_apm('color', 'card', 'black'), 'layout' => $this->style_for_apm('poweredby_tagline', 'card', \false) === $this->normalize_style_value(\true) ? 'vertical' : 'horizontal')))), 'hosted_fields' => array('wrapper' => '#ppcp-hosted-fields', 'labels' => array('credit_card_number' => '', 'cvv' => '', 'mm_yy' => __('MM/YY', 'woocommerce-paypal-payments'), 'fields_empty' => __('Card payment details are missing. Please fill in all required fields.', 'woocommerce-paypal-payments'), 'fields_not_valid' => __('Unfortunately, your credit card details are not valid.', 'woocommerce-paypal-payments'), 'card_not_supported' => __('Unfortunately, we do not support your credit card.', 'woocommerce-paypal-payments'), 'cardholder_name_required' => __('Cardholder\'s first and last name are required, please fill the checkout form required fields.', 'woocommerce-paypal-payments')), 'valid_cards' => $this->dcc_applies->valid_cards(), 'contingency' => $this->get_3ds_contingency()), 'messages' => $this->message_values(), 'labels' => array(
            'error' => array('generic' => __('Something went wrong. Please try again or choose another payment source.', 'woocommerce-paypal-payments'), 'required' => array(
                'generic' => __('Required form fields are not filled.', 'woocommerce-paypal-payments'),
                // phpcs:ignore WordPress.WP.I18n
                'field' => __('%s is a required field.', 'woocommerce'),
                'elements' => array(
                    // Map <form element name> => text for error messages.
                    'terms' => __(
                        'Please read and accept the terms and conditions to proceed with your order.',
                        // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
                        'woocommerce'
                    ),
                ),
            )),
            // phpcs:ignore WordPress.WP.I18n
            'billing_field' => _x('Billing %s', 'checkout-validation', 'woocommerce'),
            // phpcs:ignore WordPress.WP.I18n
            'shipping_field' => _x('Shipping %s', 'checkout-validation', 'woocommerce'),
        ), 'simulate_cart' => array('enabled' => apply_filters('woocommerce_paypal_payments_simulate_cart_enabled', \true), 'throttling' => apply_filters('woocommerce_paypal_payments_simulate_cart_throttling', 5000)), 'order_id' => 'pay-now' === $current_context ? $this->get_order_pay_id() : 0, 'order_key' => 'pay-now' === $current_context ? $this->get_order_pay_key() : '', 'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location('product'), 'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location('mini-cart'), 'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled, 'early_checkout_validation_enabled' => $this->early_validation_enabled, 'funding_sources_without_redirect' => $this->funding_sources_without_redirect, 'user' => array('is_logged' => is_user_logged_in(), 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens(get_current_user_id())), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && !$this->context->is_checkout(), 'server_side_shipping_callback' => array('enabled' => $this->server_side_shipping_callback_enabled), 'appswitch' => array('enabled' => $this->appswitch_enabled), 'needShipping' => $this->need_shipping(), 'vaultingEnabled' => $this->settings_provider->save_paypal_and_venmo(), 'productType' => null, 'manualRenewalEnabled' => $this->subscription_helper->accept_manual_renewals(), 'final_review_enabled' => $this->final_review_enabled);
        if (is_product()) {
            $product = wc_get_product(get_the_ID());
            if ($product instanceof \WC_Product) {
                $localize['productType'] = $product->get_type();
            }
        }
        if ('pay-now' === $current_context) {
            $localize['pay_now'] = $this->pay_now_script_data();
        }
        if ($this->context->is_paypal_continuation()) {
            $order = $this->session_handler->order();
            assert($order !== null);
            $localize['continuation'] = array('order_id' => $order->id());
        }
        $this->request_data->dequeue_nonce_fix();
        return apply_filters('woocommerce_paypal_payments_localized_script_data', $localize);
    }
    /**
     * Returns pay-now payment data.
     *
     * @return array
     */
    private function pay_now_script_data(): array
    {
        $order_id = $this->get_order_pay_id();
        $base_location = wc_get_base_location();
        $shop_country_code = $base_location['country'] ?? '';
        $currency_code = get_woocommerce_currency();
        $wc_order = wc_get_order($order_id);
        if (!$wc_order instanceof WC_Order) {
            return array();
        }
        $order_key = $this->get_order_pay_key();
        //phpcs:ignore WordPress.WP.Capabilities.Unknown
        if (!$wc_order->key_is_valid($order_key) || !current_user_can('view_order', $order_id)) {
            return array();
        }
        $total = (float) $wc_order->get_total('numeric');
        return array('total' => $total, 'total_str' => (new Money($total, $currency_code))->value_str(), 'currency_code' => $currency_code, 'country_code' => $shop_country_code);
    }
    /**
     * If we can find the payer data for a current customer, we will return it.
     *
     * @return array|null
     */
    private function payerData()
    {
        $customer = WC()->customer;
        if (!is_user_logged_in() || !$customer instanceof \WC_Customer) {
            return null;
        }
        return $this->payer_factory->from_customer($customer)->to_array();
    }
    /**
     * The JavaScript SDK url parameters.
     *
     * @return array
     */
    private function url_params(): array
    {
        $current_context = $this->context->context();
        $intent = $this->intent();
        $subscription_mode = ($this->get_subscriptions_mode)();
        $params = array('client-id' => $this->client_id, 'currency' => $this->currency->get(), 'integration-date' => PAYPAL_INTEGRATION_DATE, 'components' => implode(',', $this->components()), 'vault' => $this->can_save_vault_token() || $this->subscription_helper->need_subscription_intent($subscription_mode) ? 'true' : 'false', 'commit' => in_array($current_context, $this->pay_now_contexts, \true) ? 'true' : 'false', 'intent' => $intent);
        if (defined('WP_DEBUG') && WP_DEBUG) {
            // @phpstan-ignore booleanAnd.rightAlwaysFalse
            $params['debug'] = \true;
        }
        if ($subscription_mode === 'vaulting_api' && apply_filters('woocommerce_paypal_payments_save_payment_methods_eligible', \false)) {
            // Remove vault parameter to allow for Venmo with Save Payment Methods (Vault V3).
            unset($params['vault']);
        }
        if ($this->environment->is_sandbox() && defined('WP_DEBUG') && \WP_DEBUG && WC()->customer instanceof \WC_Customer && WC()->customer->get_billing_country() && 2 === strlen(WC()->customer->get_billing_country())) {
            $params['buyer-country'] = WC()->customer->get_billing_country();
        }
        if ('pay-now' === $current_context) {
            $wc_order_id = $this->get_order_pay_id();
            if ($wc_order_id) {
                $wc_order = wc_get_order($wc_order_id);
                if ($wc_order instanceof WC_Order) {
                    $currency = $wc_order->get_currency();
                    if ($currency) {
                        $params['currency'] = $currency;
                    }
                }
            }
        }
        $disabled_funding_sources = $this->disabled_funding_sources->sources($current_context);
        $enable_funding = array();
        $methods = $this->settings_provider->button_styling($current_context);
        if ($this->settings_provider->venmo_enabled() && in_array('venmo', $methods->methods, \true)) {
            $enable_funding[] = 'venmo';
        }
        if ($this->is_pay_later_button_enabled_for_location($current_context)) {
            $enable_funding[] = 'paylater';
        } else {
            $disabled_funding_sources[] = 'paylater';
        }
        if (count($disabled_funding_sources) > 0) {
            $params['disable-funding'] = implode(',', array_unique($disabled_funding_sources));
        }
        if ($this->is_free_trial_cart()) {
            $enable_funding = array();
        }
        if (count($enable_funding) > 0) {
            $params['enable-funding'] = implode(',', array_unique($enable_funding));
        }
        $locale = $this->settings_provider->button_language();
        $locale = (string) apply_filters('woocommerce_paypal_payments_smart_buttons_locale', $locale);
        if ($locale) {
            $params['locale'] = $locale;
        }
        return $params;
    }
    /**
     * The attributes we need to load for the JS SDK.
     *
     * @return array
     */
    private function attributes(): array
    {
        $attributes = array('data-partner-attribution-id' => $this->bn_code_for_context($this->context->context()));
        $page_type_attribute = $this->page_type_attribute();
        if ($page_type_attribute) {
            $attributes['data-page-type'] = $page_type_attribute;
        }
        return $attributes;
    }
    /**
     * Retrieves the value for page type attribute(data-page-type) used for the JS SDK.
     *
     * @return string
     */
    protected function page_type_attribute(): string
    {
        if (is_search()) {
            return 'search-results';
        }
        switch ($this->context->location()) {
            case 'product':
                return 'product-details';
            case 'shop':
                return 'product-listing';
            case 'home':
                return 'mini-cart';
            case 'cart-block':
            case 'cart':
                return 'cart';
            case 'checkout-block':
            case 'checkout':
            case 'pay-now':
                return 'checkout';
            default:
                return '';
        }
    }
    /**
     * What BN Code to use in a given context.
     *
     * @param string $context The context.
     * @return string
     */
    private function bn_code_for_context(string $context): string
    {
        $codes = $this->bn_codes();
        return isset($codes[$context]) ? $codes[$context] : $this->partner_attribution->get_bn_code();
    }
    /**
     * BN Codes to use.
     *
     * @return array
     */
    private function bn_codes(): array
    {
        $bn_code = $this->partner_attribution->get_bn_code();
        return array('checkout' => $bn_code, 'cart' => $bn_code, 'mini-cart' => $bn_code, 'product' => $bn_code);
    }
    /**
     * The JS SKD components we need to load.
     *
     * @return array
     */
    private function components(): array
    {
        $components = array();
        if ($this->should_load_buttons()) {
            $components[] = 'buttons';
            $components[] = 'funding-eligibility';
        }
        if ($this->should_load_messages()) {
            $components[] = 'messages';
        }
        // Card payments are only available on a checkout page.
        if (is_checkout() && $this->dcc_configuration->is_bcdc_enabled()) {
            $components[] = 'hosted-fields';
        }
        /**
         * Filter to add further components from the extensions.
         *
         * @internal Matches filter name in APM extension.
         *
         * @param array  $components The array of components already registered.
         * @param string $context    The SmartButton context.
         */
        return array_unique((array) apply_filters('woocommerce_paypal_payments_sdk_components_hook', $components, $this->context->context()));
    }
    /**
     * Determines the style for a given property in a given context.
     *
     * @param string $style The name of the style property.
     * @param string $context The context.
     * @param ?mixed $default The default value.
     *
     * @return string|int
     */
    private function style_for_context(string $style, string $context, $default = null)
    {
        if ($context === 'checkout-block') {
            $context = 'checkout-block-express';
        }
        if ($context === 'pay-now') {
            $context = CartCheckoutDetector::has_block_checkout() ? 'checkout-block-express' : 'checkout';
        }
        $defaults = array('layout' => 'vertical', 'size' => 'responsive', 'color' => 'gold', 'shape' => 'pill', 'label' => 'paypal', 'tagline' => \true);
        return $this->get_style_value("button_{$context}_{$style}") ?? $this->get_style_value("button_{$style}") ?? ($default ? $this->normalize_style_value($default) : null) ?? $this->normalize_style_value($defaults[$style] ?? '');
    }
    /**
     * Determines the style for a given property in a given APM.
     *
     * @param string $style The name of the style property.
     * @param string $apm The APM name, such as 'card'.
     * @param ?mixed $default The default value.
     *
     * @return string|int
     */
    private function style_for_apm(string $style, string $apm, $default = null)
    {
        return $this->get_style_value("{$apm}_button_{$style}") ?? ($default ? $this->normalize_style_value($default) : null) ?? $this->style_for_context($style, 'checkout');
    }
    /**
     * Returns the style property value or null.
     *
     * @param string $key The style property key in the settings.
     * @return string|int|null
     */
    private function get_style_value(string $key)
    {
        if (substr($key, 0, 7) !== 'button_') {
            return null;
        }
        $key_without_prefix = (string) substr($key, strlen('button_'));
        $property = $this->extract_style_property($key_without_prefix);
        if (!$property) {
            return null;
        }
        $context = substr($key_without_prefix, 0, -strlen("_{$property}"));
        if (empty($context)) {
            return null;
        }
        $location_styling = $this->get_location_styling_for_context($context);
        if (!$location_styling) {
            return null;
        }
        $value = $location_styling->{$property} ?? null;
        return $value !== null ? $this->normalize_style_value($value) : null;
    }
    /**
     * Extracts the style property from the key suffix.
     *
     * @param string $key_without_prefix The key without the 'button_' prefix.
     * @return string|null The property name or null if not found.
     */
    private function extract_style_property(string $key_without_prefix): ?string
    {
        $valid_properties = array('shape', 'label', 'color', 'layout', 'tagline');
        foreach ($valid_properties as $property) {
            $suffix = "_{$property}";
            if (substr($key_without_prefix, -strlen($suffix)) === $suffix) {
                return $property;
            }
        }
        return null;
    }
    /**
     * Gets the LocationStylingDTO for a given context.
     *
     * @param string $context The context name.
     * @return \WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO|null
     */
    private function get_location_styling_for_context(string $context)
    {
        switch ($context) {
            case 'cart':
            case 'cart-block':
                return $this->settings_provider->styling_cart();
            case 'product':
                return $this->settings_provider->styling_product();
            case 'checkout':
            case 'general':
                return $this->settings_provider->styling_classic_checkout();
            case 'checkout-block-express':
            case 'express':
                return $this->settings_provider->styling_express_checkout();
            case 'mini-cart':
            case 'minicart':
                return $this->settings_provider->styling_mini_cart();
            default:
                return null;
        }
    }
    /**
     * Converts the style property value to string.
     *
     * @param mixed $value The style property value.
     * @return string|int
     */
    private function normalize_style_value($value)
    {
        if (is_bool($value)) {
            $value = $value ? 'true' : 'false';
        }
        if (is_int($value)) {
            return $value;
        }
        return (string) $value;
    }
    /**
     * Fixes the style.
     *
     * @param array $style The style properties.
     * @return array
     */
    private function normalize_style(array $style): array
    {
        if (array_key_exists('tagline', $style) && (!array_key_exists('layout', $style) || $style['layout'] !== 'horizontal')) {
            $style['tagline'] = \false;
        }
        if (array_key_exists('height', $style) && !$style['height']) {
            unset($style['height']);
        }
        return $style;
    }
    /**
     * Returns a number between min and max.
     *
     * @param mixed $height The input value.
     * @param int   $min The minimum value.
     * @param int   $max The maximum value.
     * @return int The normalized output value.
     */
    private function normalize_height($height, int $min, int $max): int
    {
        $height = (int) $height;
        if ($height < $min) {
            return $min;
        }
        if ($height > $max) {
            return $max;
        }
        return $height;
    }
    /**
     * Returns the action name that PayPal button will use for rendering on the checkout page.
     *
     * @return string Action name.
     */
    private function checkout_button_renderer_hook(): string
    {
        /**
         * The filter returning the action name that PayPal button will use for rendering on the checkout page.
         */
        return (string) apply_filters('woocommerce_paypal_payments_checkout_button_renderer_hook', 'woocommerce_review_order_after_payment');
    }
    /**
     * Returns the action name that PayPal DCC button will use for rendering on the checkout page.
     *
     * @return string
     */
    private function checkout_dcc_button_renderer_hook(): string
    {
        /**
         * The filter returning the action name that PayPal DCC button will use for rendering on the checkout page.
         */
        return (string) apply_filters('woocommerce_paypal_payments_checkout_dcc_renderer_hook', 'woocommerce_review_order_after_submit');
    }
    /**
     * Returns the action name that PayPal button and Pay Later message will use for rendering on the pay-order page.
     *
     * @return string
     */
    private function pay_order_renderer_hook(): string
    {
        /**
         * The filter returning the action name that PayPal button and Pay Later message will use for rendering on the pay-order page.
         */
        return (string) apply_filters('woocommerce_paypal_payments_pay_order_dcc_renderer_hook', 'woocommerce_pay_order_after_submit');
    }
    /**
     * Returns the action name that will be used for rendering Pay Later messages.
     *
     * @param string $location The location name like 'checkout', 'shop'. See render_message_wrapper_registrar.
     * @param string $default_hook The default name of the hook.
     * @param int    $default_priority The default priority of the hook.
     * @return array An array with 'name' and 'priority' keys.
     */
    private function messages_renderer_hook(string $location, string $default_hook, int $default_priority): array
    {
        $location_hook = $this->location_to_hook($location);
        /**
         * The filter returning the action name that will be used for rendering Pay Later messages.
         */
        $hook = (string) apply_filters("woocommerce_paypal_payments_{$location_hook}_messages_renderer_hook", $default_hook);
        /**
         * The filter returning the action priority that will be used for rendering Pay Later messages.
         */
        $priority = (int) apply_filters("woocommerce_paypal_payments_{$location_hook}_messages_renderer_priority", $default_priority);
        return array('name' => $hook, 'priority' => $priority);
    }
    /**
     * Returns the block name that will be used for rendering Pay Later messages after.
     *
     * @param string $location The location name like 'checkout', 'shop'. See render_message_wrapper_registrar.
     * @param string $default_block The default name of the block.
     * @param int    $default_priority The default priority of the 'render_block' hook.
     * @return array An array with 'blockName' and 'priority' keys.
     */
    private function messages_renderer_block(string $location, string $default_block, int $default_priority): array
    {
        $location_hook = $this->location_to_hook($location);
        /**
         * The filter returning the action name that will be used for rendering Pay Later messages.
         */
        $block_name = (string) apply_filters("woocommerce_paypal_payments_{$location_hook}_messages_renderer_block", $default_block);
        /**
         * The filter returning the action priority that will be used for rendering Pay Later messages.
         */
        $priority = (int) apply_filters("woocommerce_paypal_payments_{$location_hook}_messages_renderer_block_priority", $default_priority);
        return array('blockName' => $block_name, 'priority' => $priority);
    }
    /**
     * Returns action name that PayPal button will use for rendering next to Proceed to checkout button (normally displayed in cart).
     *
     * @return string
     */
    private function proceed_to_checkout_button_renderer_hook(): string
    {
        /**
         * The filter returning the action name that PayPal button will use for rendering next to Proceed to checkout button (normally displayed in cart).
         */
        return (string) apply_filters('woocommerce_paypal_payments_proceed_to_checkout_button_renderer_hook', 'woocommerce_proceed_to_checkout');
    }
    /**
     * Returns the action name that PayPal button will use for rendering in the WC mini cart.
     *
     * @return string
     */
    private function mini_cart_button_renderer_hook(): string
    {
        /**
         * The filter returning the action name that PayPal button will use for rendering in the WC mini cart.
         */
        return (string) apply_filters('woocommerce_paypal_payments_mini_cart_button_renderer_hook', 'woocommerce_widget_shopping_cart_after_buttons');
    }
    /**
     * Returns the action name that PayPal button and Pay Later message will use for rendering on the single product page.
     *
     * @return string
     */
    private function single_product_renderer_hook(): string
    {
        /**
         * The filter returning the action name that PayPal button and Pay Later message will use for rendering on the single product page.
         */
        return (string) apply_filters('woocommerce_paypal_payments_single_product_renderer_hook', 'woocommerce_single_product_summary');
    }
    /**
     * Check if cart product price total is 0.
     *
     * @return bool true if is 0, otherwise false.
     * @psalm-suppress RedundantConditionGivenDocblockType
     */
    protected function is_cart_price_total_zero(): bool
    {
        // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
        return WC()->cart && WC()->cart->get_total('numeric') == 0;
    }
    /**
     * Checks if PayPal buttons/messages can be rendered for the given product.
     *
     * @param WC_Product $product The product.
     *
     * @return bool
     */
    protected function product_supports_payment(WC_Product $product): bool
    {
        /**
         * The filter returning true if PayPal buttons/messages can be rendered for this product, or false otherwise.
         */
        $in_stock = $product->is_in_stock();
        if ($product->is_type('variable')) {
            assert($product instanceof WC_Product_Variable);
            /** @var WC_Product_Variation[] $variations */
            $variations = $product->get_available_variations('objects');
            $in_stock = $this->has_in_stock_variation($variations);
        }
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        $enable_button = !$product->is_type(array('external', 'grouped')) && $in_stock && !(($product->is_type('subscription') || $product->is_type('variable-subscription')) && !empty($_GET['switch-subscription']));
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        /**
         * Allows to filter if PayPal buttons/messages can be rendered for the given product.
         */
        return apply_filters('woocommerce_paypal_payments_product_supports_payment_request_button', $enable_button, $product);
    }
    /**
     * Fills and returns the product context_data array to be used in filters.
     *
     * @param array $context_data The context data for this filter.
     * @return array
     */
    private function product_filter_context_data(array $context_data = array()): array
    {
        if (!isset($context_data['product'])) {
            $context_data['product'] = wc_get_product();
        }
        if (!$context_data['product']) {
            return array();
        }
        if (!isset($context_data['order_total']) && $context_data['product'] instanceof WC_Product) {
            $context_data['order_total'] = (float) $context_data['product']->get_price('raw');
        }
        return $context_data;
    }
    /**
     * Checks if PayPal buttons/messages should be rendered for the current page.
     *
     * @param string|null $context The context that should be checked, use default otherwise.
     * @param array       $context_data The context data for this filter.
     * @return bool
     */
    public function is_button_disabled(?string $context = null, array $context_data = array()): bool
    {
        if (null === $context) {
            $context = $this->context->context();
        }
        if ('product' === $context) {
            /**
             * Allows to decide if the button should be disabled for a given product.
             */
            return apply_filters('woocommerce_paypal_payments_product_buttons_disabled', \false, $this->product_filter_context_data($context_data));
        }
        /**
         * Allows to decide if the button should be disabled globally or on a given context.
         */
        return apply_filters('woocommerce_paypal_payments_buttons_disabled', \false, $context);
    }
    /**
     * Checks a filter if pay_later/messages should be rendered on a given location / context.
     *
     * @param string $location The location.
     * @param array  $context_data The context data for this filter.
     * @return bool
     */
    private function is_pay_later_filter_enabled_for_location(string $location, array $context_data = array()): bool
    {
        if ('product' === $location) {
            /**
             * Allows to decide if the button should be disabled for a given product.
             */
            return !apply_filters('woocommerce_paypal_payments_product_buttons_paylater_disabled', \false, $this->product_filter_context_data($context_data));
        }
        /**
         * Allows to decide if the button should be disabled on a given context.
         */
        return !apply_filters('woocommerce_paypal_payments_buttons_paylater_disabled', \false, $location);
    }
    /**
     * Check whether Pay Later button is enabled for a given location.
     *
     * @param string $location The location.
     * @param array  $context_data The context data for this filter.
     * @return bool true if is enabled, otherwise false.
     */
    public function is_pay_later_button_enabled_for_location(string $location, array $context_data = array()): bool
    {
        return $this->is_pay_later_filter_enabled_for_location($location, $context_data) && $this->settings_status->is_pay_later_button_enabled_for_location($location);
    }
    /**
     * Check whether Pay Later message is enabled for a given location.
     *
     * @param string $location The location setting name.
     * @param array  $context_data The context data for this filter.
     * @return bool true if is enabled, otherwise false.
     */
    public function is_pay_later_messaging_enabled_for_location(string $location, array $context_data = array()): bool
    {
        return $this->is_pay_later_filter_enabled_for_location($location, $context_data) && $this->settings_status->is_pay_later_messaging_enabled_for_location($location);
    }
    /**
     * Checks if variations contain any in stock variation.
     *
     * @param WC_Product_Variation[] $variations The list of variations.
     * @return bool True if any in stock variation, false otherwise.
     */
    protected function has_in_stock_variation(array $variations): bool
    {
        foreach ($variations as $variation) {
            if ($variation->is_in_stock()) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * Returns the intent.
     *
     * @return string
     */
    private function intent(): string
    {
        $subscription_mode = ($this->get_subscriptions_mode)();
        if ($this->subscription_helper->need_subscription_intent($subscription_mode)) {
            return 'subscription';
        }
        $intent = $this->settings_provider->payment_intent();
        return strtolower(apply_filters('woocommerce_paypal_payments_order_intent', $intent));
    }
    /**
     * Returns the ID of WC order on the order-pay page, or 0.
     *
     * @return int
     */
    protected function get_order_pay_id(): int
    {
        global $wp;
        if (!isset($wp->query_vars['order-pay'])) {
            return 0;
        }
        return absint($wp->query_vars['order-pay']);
    }
    /**
     * Returns the order key from the pay-for-order page URL, or empty string.
     *
     * @return string
     */
    protected function get_order_pay_key(): string
    {
        //phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $key = isset($_GET['key']) ? wc_clean(wp_unslash($_GET['key'])) : '';
        return is_string($key) ? $key : '';
    }
    /**
     * Sanitize woocommerce filter on unexpected states.
     *
     * @return void
     */
    private function sanitize_woocommerce_filters(): void
    {
        add_filter(
            'woocommerce_widget_cart_is_hidden',
            /**
             * Sometimes external plugins like "woocommerce-one-page-checkout" set the $value to null, handle that case here.
             * Here we also disable the mini-cart on cart-block and checkout-block pages where our buttons aren't supported yet.
             *
             * @psalm-suppress MissingClosureParamType
             */
            function ($value) {
                if (null === $value) {
                    if (is_product()) {
                        return \false;
                    }
                    return in_array($this->context->context(), array('cart', 'checkout', 'cart-block', 'checkout-block'), \true);
                }
                return in_array($this->context->context(), array('cart-block', 'checkout-block'), \true) ? \true : $value;
            },
            11
        );
    }
    /**
     * Converts the location name into the name used in hooks.
     *
     * @param string $location The location.
     * @return string
     */
    private function location_to_hook(string $location): string
    {
        switch ($location) {
            case 'pay-now':
                return 'pay_order';
            default:
                return $location;
        }
    }
    /**
     * Whether the given user has WC card payment tokens.
     *
     * @param int $user_id The user ID.
     * @return bool
     */
    private function user_has_wc_card_payment_tokens(int $user_id): bool
    {
        $tokens = WC_Payment_Tokens::get_customer_tokens($user_id, CreditCardGateway::ID);
        if ($tokens) {
            return \true;
        }
        return \false;
    }
}
