<?php
/**
 * Shared WooCommerce checkout request context for captcha modules.
 *
 * Two situations make a checkout captcha reject shoppers who did nothing wrong,
 * and every captcha module needs the same answer to both:
 *
 * 1. Account creation during checkout. WooCommerce funnels checkout signups
 *    through the same `woocommerce_registration_errors` filter as the My Account
 *    register form, inside the same request that already verified the checkout
 *    token. Tokens are single-use, so re-verifying burns the token
 *    (timeout-or-duplicate) — and the checkout form never rendered a register
 *    widget in the first place. Checkout validation owns that request.
 *
 * 2. Express wallet payments (Apple Pay / Google Pay / Stripe Link). The wallet
 *    sheet replaces the checkout form, the widget is never shown or solvable,
 *    so requiring a token guarantees rejection.
 *
 * @package WP_PowerSuite\Captcha\Shared
 * @since 1.1.0
 */

namespace WP_PowerSuite\Captcha\Shared;

final class WooCheckoutContext {

	/**
	 * Whether the current registration validation is running inside a checkout flow.
	 *
	 * Classic checkout fires `woocommerce_checkout_process` before it creates the
	 * account; Blocks checkout creates accounts inside a Store API (REST) request.
	 * XML-RPC never renders a widget at all. In each of those cases the checkout
	 * validator (or nothing) is authoritative and the register check must stand down.
	 */
	public static function registrationIsPartOfCheckout(): bool {
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}
		if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
			return true;
		}

		return did_action( 'woocommerce_checkout_process' ) > 0;
	}

	/**
	 * Whether a Store API checkout request pays via an express wallet.
	 *
	 * @param \WP_REST_Request $request Store API checkout request.
	 */
	public static function isExpressPayStoreApi( \WP_REST_Request $request ): bool {
		$payment_method = (string) ( $request->get_param( 'payment_method' ) ?? '' );
		$payment_data   = $request->get_param( 'payment_data' );

		$express = false;
		if ( is_array( $payment_data ) ) {
			foreach ( $payment_data as $item ) {
				if (
					is_array( $item )
					&& isset( $item['key'], $item['value'] )
					&& in_array( $item['key'], [ 'express_payment_type', 'payment_request_type' ], true )
					&& ! empty( $item['value'] )
				) {
					$express = true;
					break;
				}
			}
		}

		return self::filterExpressSkip( $payment_method, $express, $payment_data );
	}

	/**
	 * Whether a classic checkout POST pays via an express wallet
	 * (Stripe / WooPayments payment request buttons post these markers).
	 */
	public static function isExpressPayClassic(): bool {
		// phpcs:disable WordPress.Security.NonceVerification.Missing -- WooCommerce owns nonce handling for its checkout POST; this only inspects gateway markers.
		$payment_method = isset( $_POST['payment_method'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['payment_method'] ) ) : '';

		$express = false;
		foreach ( [ 'payment_request_type', 'express_payment_type' ] as $marker ) {
			if ( isset( $_POST[ $marker ] ) && is_string( $_POST[ $marker ] ) && $_POST[ $marker ] !== '' ) {
				$express = true;
				break;
			}
		}
		// phpcs:enable WordPress.Security.NonceVerification.Missing

		return self::filterExpressSkip( $payment_method, $express, null );
	}

	/**
	 * Only Stripe-family gateways are known to submit wallet payments without the
	 * checkout form; other gateways keep full protection unless a site opts in
	 * through the filter.
	 *
	 * @param string $payment_method Gateway id from the request.
	 * @param bool   $express        Whether express wallet markers were detected.
	 * @param mixed  $payment_data   Raw payment data for filter consumers.
	 */
	private static function filterExpressSkip( string $payment_method, bool $express, $payment_data ): bool {
		$skip = $express && in_array( $payment_method, [ 'woocommerce_payments', 'stripe' ], true );

		/**
		 * Whether to skip captcha verification for an express wallet checkout.
		 *
		 * @param bool   $skip           Computed default.
		 * @param string $payment_method Gateway id from the request.
		 * @param mixed  $payment_data   Raw payment data (Store API) or null (classic).
		 */
		return (bool) apply_filters( 'wp_powersuite_captcha_skip_express_pay', $skip, $payment_method, $payment_data );
	}
}
