<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase, WordPress.Files.FileName.InvalidClassFileName
/**
 * Payment Hooks Provider
 *
 * Centralizes all WordPress hook registrations for the payments module.
 * Absorbs the hook callbacks and registrations from the bottom of the
 * legacy src/payments.php file.
 *
 * @package Autoship Cloud
 * @since 2.13.0
 */

namespace Autoship\Modules\Payments;

use Autoship\Core\Plugin;
use Autoship\Domain\AbstractPaymentGateway;
use Autoship\Domain\PaymentIntegrationFactory;
use Autoship\Domain\PaymentIntegrations\SquarePaymentIntegration;
use Autoship\Modules\Payments\Services\PaymentGatewayRegistry;
use Autoship\Modules\Payments\Services\PaymentMethodService;
use Autoship\Modules\Payments\Services\PaymentMethodTypeRegistry;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;

/**
 * Registers all payment-related WordPress hooks and provides callback implementations.
 *
 * @package Autoship\Modules\Payments
 * @since 2.13.0
 */
class PaymentHooksProvider {

	/**
	 * The payment gateway registry.
	 *
	 * @var PaymentGatewayRegistry
	 */
	private PaymentGatewayRegistry $registry;

	/**
	 * The payment method type registry.
	 *
	 * @var PaymentMethodTypeRegistry
	 */
	private PaymentMethodTypeRegistry $type_registry;

	/**
	 * Constructor.
	 *
	 * @param PaymentGatewayRegistry    $registry      The gateway registry.
	 * @param PaymentMethodTypeRegistry $type_registry The type registry.
	 */
	public function __construct( PaymentGatewayRegistry $registry, PaymentMethodTypeRegistry $type_registry ) {
		$this->registry      = $registry;
		$this->type_registry = $type_registry;
	}

	/**
	 * Register all payment hooks.
	 *
	 * Called during module boot after the gateway registry is initialized.
	 *
	 * @return void
	 */
	public function register(): void {
		$this->register_token_hooks();
		$this->register_ui_hooks();
		$this->register_checkout_hooks();
		$this->register_metadata_hooks();
		$this->register_stripe_hooks();
		$this->register_nmi_hooks();
		$this->register_tokenization_filter_hooks();
		$this->register_gateway_specific_hooks();
		$this->register_skyverge_hooks();
		$this->register_sage_hooks();
	}

	/**
	 * Register token add/delete hooks.
	 *
	 * @return void
	 */
	private function register_token_hooks(): void {
		// Core token hooks - delegate to PaymentMethodService.
		add_action( 'woocommerce_new_payment_token', array( $this, 'handle_add_tokenized_payment_method' ), 10, 1 );
		add_action( 'woocommerce_payment_token_deleted', array( $this, 'handle_delete_tokenized_payment_method' ), 10, 2 );

		// My Account update flow.
		add_action( 'wp', array( $this, 'handle_update_payment_method_on_all_scheduled_orders' ), 20 );
	}

	/**
	 * Register UI hooks (notices, buttons).
	 *
	 * @return void
	 */
	private function register_ui_hooks(): void {
		add_action( 'woocommerce_new_payment_token', array( $this, 'after_save_standard_payment_method_notice' ), 10, 1 );
		add_action( 'woocommerce_after_account_payment_methods', array( $this, 'display_apply_to_all_orders_note' ), 99, 1 );
		add_filter( 'woocommerce_payment_methods_list_item', array( $this, 'display_apply_to_all_orders_btn' ), 99, 2 );

		// Skyverge gateway buttons.
		add_filter( 'wc_braintree_my_payment_methods_table_method_actions', array( $this, 'display_braintree_apply_btn' ), 10, 3 );
		add_filter( 'wc_authorize_net_cim_my_payment_methods_table_method_actions', array( $this, 'display_authorize_net_apply_btn' ), 10, 3 );
		add_filter( 'wc_square_my_payment_methods_table_method_actions', array( $this, 'display_square_apply_btn' ), 10, 3 );
		add_filter( 'wc_cybersource_credit_card_my_payment_methods_table_method_actions', array( $this, 'display_cybersource_cc_apply_btn' ), 10, 3 );
	}

	/**
	 * Register checkout filtering hooks.
	 *
	 * @return void
	 */
	private function register_checkout_hooks(): void {
		add_filter( 'woocommerce_available_payment_gateways', array( $this, 'filter_checkout_supported_gateways' ), 1 );
		add_filter( 'woocommerce_payment_gateways_settings', array( $this, 'adjust_payment_gateway_option_description' ), 10, 1 );
	}

	/**
	 * Register gateway metadata hooks (sandbox mode filtering).
	 *
	 * @return void
	 */
	private function register_metadata_hooks(): void {
		add_filter( 'autoship_payment_method_sandbox_metadata_field_ext', array( $this, 'sandbox_metadata_field_ext_init' ), 10, 3 );
		add_filter( 'autoship_payment_method_sandbox_metadata_field_test_ext', array( $this, 'sandbox_metadata_field_ext_types' ), 9, 2 );

		// Refund metadata adjusters — registered per-gateway via the registry.
		$this->register_refund_metadata_hooks();
	}

	/**
	 * Register refund metadata hooks for each gateway.
	 *
	 * @return void
	 */
	private function register_refund_metadata_hooks(): void {
		// Metadata hooks must be registered unconditionally (not from the registry)
		// because they fire during QPilot REST API order processing where
		// WC()->payment_gateways()->get_available_payment_gateways() may return empty.
		$gateway_ids = array(
			'authorize_net_cim_credit_card',
			'cybersource_credit_card',
			'stripe',
			'square_credit_card',
			'nmi_gateway_woocommerce_credit_card',
			'sagepaymentsusaapi',
			'trustcommerce',
			'wc_checkout_com_cards',
			'airwallex_card',
			'braintree_credit_card',
			'braintree_paypal',
		);

		foreach ( $gateway_ids as $gateway_id ) {
			add_action(
				"autoship_update_scheduled_orders_on_processing_{$gateway_id}_gateway",
				self::create_metadata_callback( $gateway_id ),
				10,
				3
			);
		}

		// Stripe fee/net metadata at priority 11.
		add_action(
			'autoship_update_scheduled_orders_on_processing_stripe_gateway',
			self::create_fees_metadata_callback( 'stripe' ),
			11,
			3
		);

		// PayPal gateway metadata hooks.
		add_action( 'autoship_update_scheduled_orders_on_processing_ppec_paypal_gateway', array( PaymentUtilities::class, 'add_adjusted_gateway_metadata_ppec_paypal' ), 10, 3 );
		add_action( 'autoship_update_scheduled_orders_on_processing_ppcp-gateway_gateway', array( PaymentUtilities::class, 'add_adjusted_gateway_metadata_ppep_paypal' ), 10, 3 );

		// Braintree alias — QPilot may return 'braintree' as the gateway name.
		add_action(
			'autoship_update_scheduled_orders_on_processing_braintree_gateway',
			self::create_metadata_callback( 'braintree_credit_card' ),
			10,
			3
		);
	}

	/**
	 * Create a lazy callback for gateway metadata that resolves the integration on-demand.
	 *
	 * @param string $gateway_id The WC gateway ID.
	 * @return callable The callback closure.
	 */
	private static function create_metadata_callback( string $gateway_id ): callable {
		return function ( $payment_meta, $order, $payment_method ) use ( $gateway_id ) {
			$gateway = self::resolve_gateway( $gateway_id );
			if ( $gateway && method_exists( $gateway, 'add_metadata' ) ) {
				$gateway->add_metadata( $payment_meta, $order, $payment_method );
			}
		};
	}

	/**
	 * Create a lazy callback for gateway fee metadata that resolves the integration on-demand.
	 *
	 * @param string $gateway_id The WC gateway ID.
	 * @return callable The callback closure.
	 */
	private static function create_fees_metadata_callback( string $gateway_id ): callable {
		return function ( $payment_meta, $order ) use ( $gateway_id ) {
			$gateway = self::resolve_gateway( $gateway_id );
			if ( $gateway && method_exists( $gateway, 'add_adjusted_gateway_metadata_fees' ) ) {
				$gateway->add_adjusted_gateway_metadata_fees( $payment_meta, $order );
			}
		};
	}

	/**
	 * Resolve a gateway integration, first from the registry, then via the factory.
	 *
	 * @param string $gateway_id The WC gateway ID.
	 * @return AbstractPaymentGateway|null The gateway instance or null.
	 */
	private static function resolve_gateway( string $gateway_id ): ?AbstractPaymentGateway {
		try {
			$container = Plugin::get_service_container();
			$registry  = $container->get( PaymentGatewayRegistry::class );
			$gateway   = $registry->get_gateway( $gateway_id );

			if ( $gateway instanceof AbstractPaymentGateway ) {
				return $gateway;
			}

			// Registry may be empty during REST requests — create via factory.
			$wc_gateways = WC()->payment_gateways->payment_gateways();
			$settings    = isset( $wc_gateways[ $gateway_id ] ) ? $wc_gateways[ $gateway_id ]->settings : array();

			$integration = PaymentIntegrationFactory::create( $gateway_id, $settings );

			return $integration instanceof AbstractPaymentGateway ? $integration : null;
		} catch ( \Exception $e ) {
			return null;
		}
	}

	/**
	 * Register Stripe-specific hooks.
	 *
	 * @return void
	 */
	private function register_stripe_hooks(): void {
		add_filter( 'wc_stripe_force_save_source', array( PaymentUtilities::class, 'stripe_force_save' ) );
		add_filter( 'wc_stripe_generate_create_intent_request', array( PaymentUtilities::class, 'stripe_intent_request_filter' ), 30, 2 );
		add_filter( 'autoship_create_scheduled_order_data', array( PaymentUtilities::class, 'stripe_link_order_mandate_data' ), 10, 2 );
	}

	/**
	 * Register NMI-specific hooks.
	 *
	 * @return void
	 */
	private function register_nmi_hooks(): void {
		add_filter( 'nmi_gateway_woocommerce_credit_card_should_force_tokenize', array( PaymentUtilities::class, 'force_nmi_payment_tokenization' ) );
		add_filter( 'wc_nmi_force_saved_card', '__return_true' );
	}

	/**
	 * Register tokenization filter hooks.
	 *
	 * @return void
	 */
	private function register_tokenization_filter_hooks(): void {
		add_filter( 'autoship_payment_method_tokenization', array( $this, 'tokenize_non_standard_methods' ), 10, 3 );
		add_filter( 'autoship_payment_method_tokenization', array( $this, 'tokenize_non_fully_implemented_token_classes' ), 11, 1 );
		add_filter( 'autoship_add_tokenized_payment_method', array( $this, 'filter_skyverge_tokens' ), 10, 3 );
		add_filter( 'autoship_delete_tokenized_payment_method_gateway_id', array( PaymentUtilities::class, 'filter_deleted_payment_method_gateway_ids' ), 10, 2 );
	}

	/**
	 * Register gateway-specific add/delete payment method filters from the registry.
	 *
	 * These filters are applied by PaymentMethodDataBuilder::build() and
	 * PaymentMethodService::delete_for_token() using the QPilot type name:
	 * - autoship_add_{Type}_payment_method
	 * - autoship_delete_{Type}_payment_method_qpilot_match
	 *
	 * @return void
	 */
	private function register_gateway_specific_hooks(): void {
		$registered_types = array();

		foreach ( $this->registry->get_all_gateways() as $gateway_id => $gateway ) {
			$type = $this->type_registry->get_method_type( $gateway_id );

			if ( empty( $type ) || isset( $registered_types[ $type ] ) ) {
				continue;
			}

			$registered_types[ $type ] = true;

			// Register the add payment method filter (sets gatewayCustomerId, etc.).
			add_filter(
				"autoship_add_{$type}_payment_method",
				array( $gateway, 'add_payment_method' ),
				10,
				3
			);

			// Register the delete payment method match filter.
			add_filter(
				"autoship_delete_{$type}_payment_method_qpilot_match",
				array( $gateway, 'delete_payment_method' ),
				10,
				4
			);
		}
	}

	// =============================================
	// Token Hook Callbacks
	// =============================================

	/**
	 * Handle adding a tokenized payment method.
	 *
	 * @param int $token_id The token ID.
	 * @return void
	 */
	public function handle_add_tokenized_payment_method( int $token_id ): void {
		$service = Plugin::get_service_container()->get( PaymentMethodService::class );
		$service->add_tokenized( $token_id );
	}

	/**
	 * Handle deleting a tokenized payment method.
	 *
	 * @param int    $token_id The token ID.
	 * @param object $token The token object.
	 * @return void
	 */
	public function handle_delete_tokenized_payment_method( int $token_id, $token ): void {
		$service = Plugin::get_service_container()->get( PaymentMethodService::class );
		$service->delete_tokenized( $token_id, $token );
	}

	// =============================================
	// My Account Update Flow
	// =============================================

	/**
	 * Handle the "Apply payment method to all scheduled orders" endpoint.
	 *
	 * Replaces autoship_update_payment_method_on_all_scheduled_orders().
	 *
	 * @return void
	 */
	public function handle_update_payment_method_on_all_scheduled_orders(): void {
		$autoship_method_id   = get_query_var( 'autoship_method_id' );
		$autoship_method_type = get_query_var( 'autoship_method_type' );

		if ( empty( $autoship_method_id ) || empty( $autoship_method_type ) ) {
			return;
		}

		wc_nocache_headers();

		$token = apply_filters( 'autoship_payment_method_tokenization', WC_Payment_Tokens::get( $autoship_method_id ), $autoship_method_id, $autoship_method_type );

		$type_registry = Plugin::get_service_container()->get( PaymentMethodTypeRegistry::class );
		$type          = $type_registry->get_method_type( $token->get_gateway_id() );

		if ( empty( $type ) || is_null( $token ) || get_current_user_id() !== $token->get_user_id() || false === wp_verify_nonce( $_REQUEST['_wpnonce'], 'autoship-apply-payment-method-to-orders-' . $autoship_method_id ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

			$message = __( 'Invalid payment method.', 'autoship' );
			$notice  = apply_filters(
				'autoship_update_payment_method_on_all_scheduled_orders_failure_invalid_notice',
				array(
					'message' => $message,
					'code'    => 'error',
				),
				$type
			);
		} elseif ( ! autoship_rights_checker( 'autoship_update_payment_info_on_all_scheduled_orders', array() ) ) {

			$message = __( 'You have insufficient rights to make this change.', 'autoship' );
			$notice  = apply_filters(
				'autoship_update_payment_method_on_all_scheduled_orders_failure_rights_notice',
				array(
					'message' => $message,
					'code'    => 'error',
				),
				$type
			);
		} else {

			$schedule_label = autoship_translate_text( 'Scheduled Orders' );

			$service              = Plugin::get_service_container()->get( PaymentMethodService::class );
			$autoship_method_data = $service->get_data_builder()->build( $token );

			$autoship_method_data['applyToScheduledOrders'] = true;

			$result = $service->upsert( $autoship_method_data );

			if ( is_wp_error( $result ) ) {
				// translators: %1$s is the label for Scheduled Orders, %2$s is the error message.
				$message = sprintf( __( 'There was a problem updating the information on your %1$s. Additional details: %2$s', 'autoship' ), $schedule_label, $result->get_error_message() );
				$notice  = apply_filters(
					'autoship_update_payment_method_on_all_scheduled_orders_failure_notice',
					array(
						'message' => $message,
						'code'    => 'error',
					),
					$type
				);
			} else {
				// translators: %s is the label for Scheduled Orders.
				$message = sprintf( __( 'Your %s Have been successfully updated.', 'autoship' ), $schedule_label );
				$notice  = apply_filters(
					'autoship_update_payment_method_on_all_scheduled_orders_updated_notice',
					array(
						'message' => $message,
						'code'    => 'success',
					),
					$type
				);
			}
		}

		wc_add_notice( $notice['message'], $notice['code'] );

		wp_redirect( wc_get_account_endpoint_url( 'payment-methods' ) ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
		exit();
	}

	// =============================================
	// UI Callbacks
	// =============================================

	/**
	 * Display save notice after a standard payment method is saved.
	 *
	 * Replaces autoship_after_save_standard_payment_method_notice().
	 *
	 * @param int $token_id The token ID.
	 * @return void
	 */
	public function after_save_standard_payment_method_notice( int $token_id ): void {
		$hide = apply_filters( 'autoship_display_save_payment_method_at_checkout', is_checkout() );

		if ( $hide ) {
			return;
		}

		$token = WC_Payment_Tokens::get( $token_id );

		$gateway_id = apply_filters( 'autoship_add_tokenized_payment_method_gateway_id', $token->get_gateway_id(), $token_id, $token );

		$type_registry  = Plugin::get_service_container()->get( PaymentMethodTypeRegistry::class );
		$valid_gateways = $type_registry->get_valid_methods();

		if ( ! array_key_exists( $gateway_id, $valid_gateways ) ) {
			return;
		}

		if ( isset( $_POST['payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			PaymentUtilities::after_save_payment_method_notice( $token->get_id(), 'standard', $token->get_display_name() );
		}
	}

	/**
	 * Display the "Apply to all orders" note on the payment methods page.
	 *
	 * Replaces autoship_display_apply_payment_method_to_all_scheduled_orders_note().
	 *
	 * @param bool $has_methods Whether any payment methods exist.
	 * @return void
	 */
	public function display_apply_to_all_orders_note( $has_methods ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
		if ( ! apply_filters( 'autoship_display_customer_update_actions', true, get_current_user_id() ) ) {
			return;
		}

		$schedule_label = autoship_translate_text( 'Scheduled Orders' );

		echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			'autoship_display_apply_method_to_all_scheduled_orders_note',
			sprintf(
				// translators: %s is the label for Scheduled Orders.
				__( '<p><strong><i class="autoship_update_all_orders"></i></strong> Select to apply the payment method to your %s.</p>', 'autoship' ),
				$schedule_label
			)
		);
	}

	/**
	 * Display the "Apply to all orders" button for standard gateways.
	 *
	 * Replaces autoship_display_apply_payment_method_to_all_scheduled_orders_btn().
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @return array The filtered list item.
	 */
	public function display_apply_to_all_orders_btn( array $list_item, $payment_token ): array {
		if ( ! apply_filters( 'autoship_display_customer_update_actions', true, get_current_user_id() ) ) {
			return $list_item;
		}

		$apply_url = PaymentUtilities::get_update_payment_method_url( $payment_token->get_id() );
		$apply_url = wp_nonce_url( $apply_url, 'autoship-apply-payment-method-to-orders-' . $payment_token->get_id() );

		$list_item['actions']['autoship_update_all_orders'] = apply_filters(
			'autoship_apply_payment_method_to_all_scheduled_orders_row_action',
			array(
				'url'  => $apply_url,
				'name' => apply_filters( 'autoship_update_orders_icon_action', '' ),
			),
			$apply_url,
			$payment_token,
			$list_item
		);

		return apply_filters( 'autoship_apply_payment_method_to_all_scheduled_orders_row', $list_item, $payment_token );
	}

	/**
	 * Display apply button for Braintree.
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @param object $instance The My Payment Methods instance.
	 * @return array The filtered list item.
	 */
	public function display_braintree_apply_btn( $list_item, $payment_token, $instance ): array {
		$gateway = $payment_token->is_paypal_account() ? 'braintree_paypal' : 'braintree_credit_card';

		return $this->display_skyverge_apply_btn( $list_item, $payment_token, $instance, $gateway );
	}

	/**
	 * Display apply button for Authorize.Net.
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @param object $instance The My Payment Methods instance.
	 * @return array The filtered list item.
	 */
	public function display_authorize_net_apply_btn( $list_item, $payment_token, $instance ): array {
		return $this->display_skyverge_apply_btn( $list_item, $payment_token, $instance, 'authorize_net_cim_credit_card' );
	}

	/**
	 * Display apply button for Square.
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @param object $instance The My Payment Methods instance.
	 * @return array The filtered list item.
	 */
	public function display_square_apply_btn( $list_item, $payment_token, $instance ): array {
		return $this->display_skyverge_apply_btn( $list_item, $payment_token, $instance, 'square_credit_card' );
	}

	/**
	 * Display apply button for CyberSource CC.
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @param object $instance The My Payment Methods instance.
	 * @return array The filtered list item.
	 */
	public function display_cybersource_cc_apply_btn( $list_item, $payment_token, $instance ): array {
		return $this->display_skyverge_apply_btn( $list_item, $payment_token, $instance, 'cybersource_credit_card' );
	}

	/**
	 * Shared implementation for Skyverge gateway apply buttons.
	 *
	 * @param array  $list_item The list item array.
	 * @param object $payment_token The payment token.
	 * @param object $instance The My Payment Methods instance.
	 * @param string $gateway The gateway ID.
	 * @return array The filtered list item.
	 */
	private function display_skyverge_apply_btn( $list_item, $payment_token, $instance, string $gateway ): array {
		if ( ! apply_filters( 'autoship_display_customer_update_actions', true, get_current_user_id() ) ) {
			return $list_item;
		}

		$apply_url = PaymentUtilities::get_update_payment_method_url( $payment_token->get_id(), $gateway );
		$apply_url = wp_nonce_url( $apply_url, 'autoship-apply-payment-method-to-orders-' . $payment_token->get_id() );

		$list_item['autoship_update_all_orders'] = apply_filters(
			'autoship_apply_payment_method_to_all_scheduled_orders_row_action',
			array(
				'url'   => $apply_url,
				'name'  => apply_filters( 'autoship_update_orders_icon_action', '' ),
				'class' => 'autoship_update_all_orders',
			),
			$apply_url,
			$payment_token,
			$list_item
		);

		return apply_filters( 'autoship_apply_payment_method_to_all_scheduled_orders_non_standard_row', $list_item, $payment_token, $instance );
	}

	// =============================================
	// Checkout Filtering
	// =============================================

	/**
	 * Filter available payment gateways at checkout to only show Autoship-supported ones.
	 *
	 * Replaces autoship_filter_checkout_supported_gateways().
	 *
	 * @param array $gateways Available payment gateways.
	 * @return array Filtered gateways.
	 */
	public function filter_checkout_supported_gateways( array $gateways ): array {
		// Delegate to legacy function until fully migrated.
		return PaymentUtilities::filter_checkout_supported_gateways( $gateways );
	}

	/**
	 * Add Autoship info to the WC payment gateway settings page.
	 *
	 * Replaces autoship_adjust_payment_gateway_option_description().
	 *
	 * @param array $settings The payment gateway settings.
	 * @return array The filtered settings.
	 */
	public function adjust_payment_gateway_option_description( array $settings ): array {
		return PaymentUtilities::adjust_payment_gateway_option_description( $settings );
	}

	// =============================================
	// Tokenization Filters
	// =============================================

	/**
	 * Tokenize non-standard payment methods (Skyverge framework).
	 *
	 * Replaces autoship_tokenize_non_standard_methods().
	 *
	 * @param object $token The current WC Payment Token.
	 * @param string $autoship_method_id The method ID.
	 * @param string $autoship_method_type The method type.
	 * @return object The tokenized payment method.
	 */
	public function tokenize_non_standard_methods( $token, $autoship_method_id, $autoship_method_type ) {
		return PaymentUtilities::tokenize_non_standard_methods( $token, $autoship_method_id, $autoship_method_type );
	}

	/**
	 * Generate WC Payment Token for gateways that don't fully implement the class.
	 *
	 * Replaces autoship_tokenize_non_fully_implemented_token_classes().
	 *
	 * @param object $token The current WC Payment Token.
	 * @return object The tokenized payment method.
	 */
	public function tokenize_non_fully_implemented_token_classes( $token ) {
		return PaymentUtilities::tokenize_non_fully_implemented_token_classes( $token );
	}

	/**
	 * Filter Skyverge tokens to prevent early upsert during checkout.
	 *
	 * Replaces autoship_filter_skyverge_tokens().
	 *
	 * @param bool   $upsert Current upsert flag.
	 * @param object $token The token object.
	 * @return bool Whether to skip upserting.
	 */
	public function filter_skyverge_tokens( $upsert, $token ): bool {
		if ( ! $token || ! ( $token instanceof WC_Payment_Token_CC ) ) {
			return true;
		}

		$type_registry   = Plugin::get_service_container()->get( PaymentMethodTypeRegistry::class );
		$valid_methods   = $type_registry->get_valid_methods();
		$skyverge_tokens = PaymentMethodTypeRegistry::get_late_token_skyverge_gateways();

		$gateway_id = $token->get_gateway_id();

		if ( isset( $valid_methods[ $gateway_id ] ) && isset( $skyverge_tokens[ $gateway_id ] ) ) {
			$upsert = true;
		}

		return $upsert;
	}

	// =============================================
	// Sandbox Metadata Callbacks
	// =============================================

	/**
	 * Determine sandbox metadata field extension based on test mode.
	 *
	 * Replaces autoship_skyverge_payment_method_sandbox_metadata_field_ext_init().
	 *
	 * @param string $ext The non-test mode extension.
	 * @param string $alt_ext The test mode extension.
	 * @param string $autoship_method_type The gateway ID.
	 * @return string The metadata field extension.
	 */
	public function sandbox_metadata_field_ext_init( string $ext, string $alt_ext, string $autoship_method_type ): string {
		return PaymentUtilities::sandbox_metadata_field_ext_init( $ext, $alt_ext, $autoship_method_type );
	}

	/**
	 * Map gateway to test mode extension prefix.
	 *
	 * Replaces autoship_payment_method_sandbox_metadata_field_ext_types().
	 *
	 * @param string $ext The current extension.
	 * @param string $autoship_method_type The gateway ID.
	 * @return string The filtered extension.
	 */
	public function sandbox_metadata_field_ext_types( string $ext, string $autoship_method_type ): string {
		return PaymentUtilities::sandbox_metadata_field_ext_types( $ext, $autoship_method_type );
	}

	// =============================================
	// Skyverge Framework Hooks
	// =============================================

	/**
	 * Register hooks for Skyverge framework gateways (non-standard tokenization).
	 *
	 * These handle payment method add/delete during checkout and My Account for
	 * legacy Skyverge gateways: AuthorizeNet, Braintree, CyberSource, Square.
	 *
	 * @return void
	 */
	private function register_skyverge_hooks(): void {
		// AuthorizeNet.
		$authnet = $this->registry->get_gateway( 'authorize_net_cim_credit_card' );
		if ( $authnet ) {
			add_filter( 'wc_payment_gateway_authorize_net_cim_credit_card_add_payment_method_transaction_result', array( $authnet, 'add_transaction_payment_method' ), 10, 4 );
			add_action( 'wc_payment_gateway_authorize_net_cim_credit_card_payment_processed', array( $authnet, 'add_skyverge_payment_method' ), 10, 2 );
			add_action( 'wc_payment_gateway_authorize_net_cim_credit_card_payment_method_added', array( $authnet, 'add_my_account_skyverge_payment_method' ), 10, 3 );
			add_action( 'wc_payment_gateway_authorize_net_cim_credit_card_payment_method_deleted', array( $authnet, 'handle_skyverge_payment_method_deleted' ), 10, 2 );
			add_action( 'wc_payment_gateway_authorize_net_cim_credit_card_payment_method_added', array( $authnet, 'after_save_authorize_net_payment_method_notice' ), 10, 3 );
		}

		// CyberSource V2.
		$cybersource = $this->registry->get_gateway( 'cybersource_credit_card' );
		if ( $cybersource ) {
			add_action( 'wc_payment_gateway_cybersource_credit_card_payment_processed', array( $cybersource, 'add_skyverge_payment_method' ), 10, 2 );
			add_action( 'wc_payment_gateway_cybersource_credit_card_payment_method_added', array( $cybersource, 'add_my_account_skyverge_payment_method' ), 10, 3 );

			// Legacy V1 type aliases — handle QPilot payment methods stored under the older "CyberSource" type.
			add_filter( 'autoship_add_CyberSource_payment_method', array( $cybersource, 'add_cybersource_v1_payment_method' ), 10, 3 );
			add_filter( 'autoship_delete_CyberSource_payment_method_qpilot_match', array( $cybersource, 'delete_cybersource_v1_payment_method' ), 10, 4 );
		}

		// Braintree Credit Card.
		$braintree_cc = $this->registry->get_gateway( 'braintree_credit_card' );
		if ( $braintree_cc ) {
			add_filter( 'wc_payment_gateway_braintree_credit_card_add_payment_method_transaction_result', array( $braintree_cc, 'add_credit_card_payment_method_data' ), 10, 4 );
			add_action( 'wc_payment_gateway_braintree_credit_card_payment_processed', array( $braintree_cc, 'add_skyverge_payment_method' ), 10, 2 );
			add_action( 'wc_payment_gateway_braintree_credit_card_payment_method_added', array( $braintree_cc, 'add_my_account_skyverge_payment_method' ), 10, 3 );
			add_action( 'wc_payment_gateway_braintree_credit_card_payment_method_deleted', array( $braintree_cc, 'handle_skyverge_payment_method_deleted' ), 10, 2 );
			add_action( 'wc_payment_gateway_braintree_credit_card_payment_method_added', array( $braintree_cc, 'after_save_braintree_credit_card_payment_method_notice' ), 10, 3 );
		}

		// Braintree PayPal.
		$braintree_pp = $this->registry->get_gateway( 'braintree_paypal' );
		if ( $braintree_pp ) {
			add_filter( 'wc_payment_gateway_braintree_paypal_add_payment_method_transaction_result', array( $braintree_pp, 'add_paypal_payment_method_data' ), 10, 4 );
			add_action( 'wc_payment_gateway_braintree_paypal_payment_processed', array( $braintree_pp, 'add_skyverge_payment_method' ), 10, 2 );
			add_action( 'wc_payment_gateway_braintree_paypal_payment_method_added', array( $braintree_pp, 'add_my_account_skyverge_payment_method' ), 10, 3 );
			add_action( 'wc_payment_gateway_braintree_paypal_payment_method_deleted', array( $braintree_pp, 'handle_skyverge_payment_method_deleted' ), 10, 2 );
		}

		// Square.
		$square = $this->registry->get_gateway( 'square_credit_card' );
		if ( $square ) {
			add_filter( 'wc_payment_gateway_square_credit_card_add_payment_method_transaction_result', array( $square, 'add_payment_method_data' ), 10, 4 );
			add_action( 'wc_payment_gateway_square_payment_method_added', array( $square, 'autoship_after_save_square_credit_card_payment_method_notice' ), 10, 3 );
			add_filter( 'wc_payment_gateway_square_credit_card_tokenization_forced', array( SquarePaymentIntegration::class, 'force_save_source' ) );

			// Square's token class extends WC_Payment_Token_CC, so deletion fires the WC core action instead of a Skyverge-specific one.
			add_action( 'woocommerce_payment_token_deleted', array( $square, 'handle_token_deleted' ), 10, 2 );
		}
	}

	// =============================================
	// SagePay Hooks
	// =============================================

	/**
	 * Register SagePay-specific hooks.
	 *
	 * @return void
	 */
	private function register_sage_hooks(): void {
		$sage = $this->registry->get_gateway( 'sagepaydirect' );
		if ( $sage ) {
			add_action( 'woocommerce_checkout_order_processed', array( $sage, 'checkout_order_patch' ), 10, 3 );
		}
	}
}
