<?php
/**
 * Currency conversion helpers for Stripe minor-unit math.
 *
 * Stripe represents money in the SMALLEST currency unit:
 *   - 2-decimal currencies (USD, EUR, CNY, HKD, …) → cents/fen (multiply by 100).
 *   - 0-decimal currencies (JPY, KRW, VND, CLP, BIF, …) → whole units (no multiply).
 *   - 3-decimal currencies (BHD, KWD, JOD, OMR, TND, …) → multiply by 1000.
 *
 * One wrong constant or one float-truncation here = direct 100× over- or under-
 * charge per order. This helper is the single source of truth for the plugin.
 *
 * Reference: https://docs.stripe.com/currencies
 *
 * @package ChinaPayments\Core
 */

namespace ChinaPayments\Core;

defined( 'ABSPATH' ) || exit;

/**
 * Stateless Stripe money-conversion helpers.
 */
class CurrencyHelper {

	/**
	 * Zero-decimal currencies per Stripe docs.
	 *
	 * @var array<int,string>
	 */
	protected static $zero_decimal = array(
		'BIF',
		'CLP',
		'DJF',
		'GNF',
		'JPY',
		'KMF',
		'KRW',
		'MGA',
		'PYG',
		'RWF',
		'UGX',
		'VND',
		'VUV',
		'XAF',
		'XOF',
		'XPF',
	);

	/**
	 * Three-decimal currencies per Stripe docs.
	 *
	 * @var array<int,string>
	 */
	protected static $three_decimal = array(
		'BHD',
		'JOD',
		'KWD',
		'OMR',
		'TND',
	);

	/**
	 * Convert a human-readable amount to Stripe's minor-unit integer.
	 *
	 * @param float|int|string $amount   Amount in the readable form (e.g. 9.99 for $9.99).
	 * @param string           $currency ISO 4217 code, case- and whitespace-insensitive.
	 * @return int Minor units for Stripe (e.g. 999 for $9.99, 100 for ¥100, 1500 for 1.500 BHD).
	 */
	public static function to_stripe_amount( $amount, string $currency ): int {
		$decimals = self::decimals_for( $currency );
		$factor   = (int) pow( 10, $decimals );

		// `round` (not `intval`) — `intval( 0.99 * 100 )` truncates to 98 on most
		// PHP builds because of float drift. `round()` then `(int)` is correct.
		return (int) round( ( (float) $amount ) * $factor );
	}

	/**
	 * Convert a Stripe minor-unit integer back to a readable decimal amount.
	 *
	 * @param int    $minor_units Amount in Stripe minor units.
	 * @param string $currency    ISO 4217 currency code.
	 * @return float
	 */
	public static function from_stripe_amount( int $minor_units, string $currency ): float {
		$decimals = self::decimals_for( $currency );
		$factor   = (int) pow( 10, $decimals );

		if ( $factor === 1 ) {
			return (float) $minor_units;
		}

		return $minor_units / $factor;
	}

	/**
	 * Number of decimal places for an ISO currency. Defaults to 2 for unknown codes.
	 *
	 * @param string $currency ISO 4217 currency code.
	 * @return int 0, 2, or 3.
	 */
	public static function decimals_for( string $currency ): int {
		$code = strtoupper( trim( $currency ) );

		if ( in_array( $code, self::$zero_decimal, true ) ) {
			return 0;
		}

		if ( in_array( $code, self::$three_decimal, true ) ) {
			return 3;
		}

		return 2;
	}

	/**
	 * True if the given ISO currency is zero-decimal.
	 *
	 * @param string $currency ISO 4217 currency code.
	 * @return bool
	 */
	public static function is_zero_decimal( string $currency ): bool {
		return self::decimals_for( $currency ) === 0;
	}

	/**
	 * True if the given ISO currency is three-decimal.
	 *
	 * @param string $currency ISO 4217 currency code.
	 * @return bool
	 */
	public static function is_three_decimal( string $currency ): bool {
		return self::decimals_for( $currency ) === 3;
	}

	/**
	 * Full list of Stripe zero-decimal currencies (read-only copy).
	 *
	 * @return array<int,string>
	 */
	public static function zero_decimal_currencies(): array {
		return self::$zero_decimal;
	}

	/**
	 * Full list of Stripe three-decimal currencies (read-only copy).
	 *
	 * @return array<int,string>
	 */
	public static function three_decimal_currencies(): array {
		return self::$three_decimal;
	}
}
