<?php

namespace DigitalApps\PaymentsForAdyen;

defined('ABSPATH') || exit;

/**
 * Convert display amounts to the minor units required by Adyen APIs.
 */
final class Adyen_Amount
{
	/**
	 * Currencies for which Adyen expects no decimal places.
	 *
	 * @var string[]
	 */
	private const ZERO_DECIMAL_CURRENCIES = [
		'CVE',
		'DJF',
		'GNF',
		'IDR',
		'JPY',
		'KMF',
		'KRW',
		'PYG',
		'RWF',
		'UGX',
		'VND',
		'VUV',
		'XAF',
		'XOF',
		'XPF',
	];

	/**
	 * Currencies for which Adyen expects three decimal places.
	 *
	 * @var string[]
	 */
	private const THREE_DECIMAL_CURRENCIES = [
		'BHD',
		'IQD',
		'JOD',
		'KWD',
		'LYD',
		'OMR',
		'TND',
	];

	/**
	 * Convert a major-unit amount to Adyen minor units.
	 *
	 * Adyen's currency exponents differ from ISO 4217 for some currencies, so
	 * WooCommerce's store-wide decimal setting cannot be used here.
	 *
	 * @param int|float|string $amount   Major-unit amount.
	 * @param string           $currency Three-letter currency code.
	 * @return int
	 * @throws \InvalidArgumentException When the amount or currency is invalid.
	 */
	public static function to_minor_units($amount, $currency)
	{
		if (!is_numeric($amount) || !is_string($currency) || 3 !== strlen($currency)) {
			throw new \InvalidArgumentException('A numeric amount and three-letter currency code are required.');
		}

		$numeric_amount = (float) $amount;
		if (!is_finite($numeric_amount) || $numeric_amount < 0) {
			throw new \InvalidArgumentException('The amount must be a finite, non-negative number.');
		}

		$exponent = self::currency_exponent($currency);
		$minor_units = round($numeric_amount * (10 ** $exponent), 0, PHP_ROUND_HALF_UP);

		if ($minor_units > PHP_INT_MAX) {
			throw new \InvalidArgumentException('The amount is too large to represent in minor units.');
		}

		return (int) $minor_units;
	}

	/**
	 * Get the decimal exponent used by Adyen for a currency.
	 *
	 * @param string $currency Three-letter currency code.
	 * @return int
	 */
	private static function currency_exponent($currency)
	{
		$currency = strtoupper($currency);

		if (in_array($currency, self::ZERO_DECIMAL_CURRENCIES, true)) {
			return 0;
		}

		if (in_array($currency, self::THREE_DECIMAL_CURRENCIES, true)) {
			return 3;
		}

		return 2;
	}
}
