<?php
/**
 * Gate_Context — immutable request context passed to every gate rule.
 *
 * @package TrustLens
 * @since   1.2.2
 */

defined( 'ABSPATH' ) || exit;

final class TrustLens_Gate_Context {

	public const CHECKOUT_CLASSIC = 'classic';
	public const CHECKOUT_BLOCKS  = 'blocks';

	/**
	 * Request intent — what the shopper is actually trying to do.
	 *
	 * CART_WRITE     — mutating the cart (add-item, update-item, apply-coupon,
	 *                  select-shipping-rate, update-customer, …). The shopper
	 *                  has NOT yet submitted payment.
	 * CHECKOUT_SUBMIT — submitting the checkout (Classic POST or Store API
	 *                  POST/PUT /wc/store/v1/checkout). This is the only intent
	 *                  that should count toward "submission" velocity, because
	 *                  it is the only one that represents a payment attempt.
	 *
	 * The gate runs on both intents so that rules with broader scope (e.g. the
	 * email blocklist, which must block a known-bad customer from even putting
	 * items in the cart) still fire. But downstream consumers — notably the
	 * card-testing velocity recorder — MUST branch on intent so they don't
	 * misclassify cart fiddling as payment attempts.
	 *
	 * @since 1.2.4
	 */
	public const INTENT_CART_WRITE      = 'cart_write';
	public const INTENT_CHECKOUT_SUBMIT = 'checkout_submit';

	public string $fingerprint_hash;
	public string $ip;
	public ?string $email;
	public ?int $customer_id;
	public string $checkout_type;
	public string $intent;
	public array $request_payload;

	/**
	 * Server-computed fallback fingerprint hash. Always set when the client
	 * presented a valid browser fingerprint via `X-TL-FP` / `_tl_fp` so callers
	 * can defend against client-hash rotation by also checking the server hash
	 * (the same IP + UA + Accept-Language produces a stable server hash even
	 * when the JavaScript-side hash rotates each request).
	 *
	 * Equals the empty string when the primary `fingerprint_hash` IS the
	 * server-computed fallback (i.e. no client hash was presented or it was
	 * malformed) — in that case no separate dual-record is needed.
	 *
	 * @since 1.2.3
	 */
	public string $server_fingerprint_hash = '';

	public function __construct(
		string $fingerprint_hash,
		string $ip,
		?string $email,
		?int $customer_id,
		string $checkout_type,
		array $request_payload,
		string $server_fingerprint_hash = '',
		string $intent = self::INTENT_CHECKOUT_SUBMIT
	) {
		if ( ! in_array( $checkout_type, array( self::CHECKOUT_CLASSIC, self::CHECKOUT_BLOCKS ), true ) ) {
			throw new InvalidArgumentException(
				esc_html( sprintf( 'Invalid checkout_type "%s". Expected "classic" or "blocks".', $checkout_type ) )
			);
		}
		if ( ! in_array( $intent, array( self::INTENT_CART_WRITE, self::INTENT_CHECKOUT_SUBMIT ), true ) ) {
			throw new InvalidArgumentException(
				esc_html( sprintf( 'Invalid intent "%s". Expected "cart_write" or "checkout_submit".', $intent ) )
			);
		}

		$this->fingerprint_hash        = $fingerprint_hash;
		$this->ip                      = $ip;
		$this->email                   = $email;
		$this->customer_id             = $customer_id;
		$this->checkout_type           = $checkout_type;
		$this->intent                  = $intent;
		$this->request_payload         = $request_payload;
		// Only store the server hash when it actually differs from the primary;
		// equal hashes would just be redundant work in dual-record paths.
		$this->server_fingerprint_hash = ( '' !== $server_fingerprint_hash && $server_fingerprint_hash !== $fingerprint_hash )
			? $server_fingerprint_hash
			: '';
	}
}
