<?php
/**
 * Phone/SMS normalisation to a Brevo-acceptable E.164-style value.
 *
 * @package RewindBrevoFormBridge
 */

namespace RewindBrevoFormBridge\Mapping;

defined( 'ABSPATH' ) || exit;

/**
 * Brevo requires a country code on SMS values (+8801XXXXXXXXX,
 * 008801XXXXXXXXX or 8801XXXXXXXXX) — a bare local number is rejected or
 * mis-stored. This class normalises to the "+" form. Pure PHP, hardcoded
 * dialling map, no external library, no API call.
 *
 * @since 0.2.0
 */
class Phone {

	/**
	 * ISO-2 country to dialling code, ~70 most common countries.
	 *
	 * @var array<string, string>
	 */
	const DIAL_CODES = array(
		'US' => '1',
		'CA' => '1',
		'GB' => '44',
		'IE' => '353',
		'FR' => '33',
		'DE' => '49',
		'IT' => '39',
		'ES' => '34',
		'PT' => '351',
		'NL' => '31',
		'BE' => '32',
		'CH' => '41',
		'AT' => '43',
		'SE' => '46',
		'NO' => '47',
		'DK' => '45',
		'FI' => '358',
		'IS' => '354',
		'PL' => '48',
		'CZ' => '420',
		'SK' => '421',
		'HU' => '36',
		'RO' => '40',
		'BG' => '359',
		'GR' => '30',
		'HR' => '385',
		'RS' => '381',
		'UA' => '380',
		'RU' => '7',
		'TR' => '90',
		'IL' => '972',
		'AE' => '971',
		'SA' => '966',
		'QA' => '974',
		'KW' => '965',
		'BH' => '973',
		'OM' => '968',
		'JO' => '962',
		'LB' => '961',
		'EG' => '20',
		'MA' => '212',
		'DZ' => '213',
		'TN' => '216',
		'ZA' => '27',
		'NG' => '234',
		'KE' => '254',
		'GH' => '233',
		'ET' => '251',
		'TZ' => '255',
		'BD' => '880',
		'IN' => '91',
		'PK' => '92',
		'LK' => '94',
		'NP' => '977',
		'MM' => '95',
		'TH' => '66',
		'VN' => '84',
		'MY' => '60',
		'SG' => '65',
		'ID' => '62',
		'PH' => '63',
		'CN' => '86',
		'HK' => '852',
		'TW' => '886',
		'JP' => '81',
		'KR' => '82',
		'AU' => '61',
		'NZ' => '64',
		'BR' => '55',
		'MX' => '52',
		'AR' => '54',
		'CL' => '56',
		'CO' => '57',
		'PE' => '51',
		'VE' => '58',
	);

	/**
	 * Normalise a submitted phone number to +<country><number>.
	 *
	 * @since 0.2.0
	 * @param mixed  $value                Raw submitted value.
	 * @param string $default_country_iso2 ISO-2 country used when the number
	 *                                     carries no country code. '' = none.
	 * @return array ['ok' => bool, 'value' => string|null, 'reason' => string].
	 */
	public function normalize( $value, string $default_country_iso2 = '' ): array {
		$raw = is_scalar( $value ) ? trim( (string) $value ) : '';

		if ( '' === $raw ) {
			return $this->reject( __( 'The phone field was empty.', 'rewind-form-bridge-for-brevo' ) );
		}

		// Strip spaces, dashes, parentheses and dots.
		$clean = preg_replace( '/[\s\-().]/u', '', $raw );

		// Already internationally prefixed?
		if ( 0 === strpos( $clean, '+' ) ) {
			return $this->validate_full( '+' . preg_replace( '/\D/', '', substr( $clean, 1 ) ), $raw );
		}

		if ( 0 === strpos( $clean, '00' ) ) {
			return $this->validate_full( '+' . preg_replace( '/\D/', '', substr( $clean, 2 ) ), $raw );
		}

		if ( preg_match( '/\D/', $clean ) ) {
			return $this->reject(
				sprintf(
					/* translators: %s: the rejected raw value. */
					__( '"%s" contains characters that are not part of a phone number.', 'rewind-form-bridge-for-brevo' ),
					$raw
				)
			);
		}

		// A bare national number needs a default country to become dialable.
		$iso = strtoupper( trim( $default_country_iso2 ) );

		if ( '' === $iso || ! isset( self::DIAL_CODES[ $iso ] ) ) {
			return $this->reject(
				sprintf(
					/* translators: %s: the rejected raw value. */
					__( '"%s" has no country code and no default country is configured in the plugin settings.', 'rewind-form-bridge-for-brevo' ),
					$raw
				)
			);
		}

		// Strip a single national trunk zero before prefixing: 01712345678
		// with BD becomes +8801712345678.
		$national = ltrim( $clean, '0' );
		if ( strlen( $clean ) - strlen( $national ) > 1 ) {
			// More than one leading zero is not a trunk prefix — reject
			// rather than guessing (defensive: Brevo would mis-store it).
			return $this->reject(
				sprintf(
					/* translators: %s: the rejected raw value. */
					__( 'Could not read "%s" as a phone number.', 'rewind-form-bridge-for-brevo' ),
					$raw
				)
			);
		}

		return $this->validate_national( self::DIAL_CODES[ $iso ], $national, $raw );
	}

	/**
	 * Validate a number that arrived with its own country code.
	 *
	 * The country code cannot be split off without a full metadata library,
	 * so the plausibility check runs on the total digit count (E.164 allows
	 * at most 15 digits; anything under 8 cannot contain code + subscriber).
	 *
	 * @param string $candidate "+" followed by digits only.
	 * @param string $raw       Original value for messages.
	 * @return array Result triple.
	 */
	private function validate_full( string $candidate, string $raw ): array {
		$digits = strlen( $candidate ) - 1;

		if ( $digits < 8 || $digits > 15 ) {
			return $this->reject(
				sprintf(
					/* translators: %s: the rejected raw value. */
					__( '"%s" is not a plausible international phone number (wrong length).', 'rewind-form-bridge-for-brevo' ),
					$raw
				)
			);
		}

		return $this->accept( $candidate );
	}

	/**
	 * Validate a national number being given our default country code.
	 *
	 * @param string $dial     Dialling code, e.g. '880'.
	 * @param string $national Subscriber part, trunk zero stripped.
	 * @param string $raw      Original value for messages.
	 * @return array Result triple.
	 */
	private function validate_national( string $dial, string $national, string $raw ): array {
		$len = strlen( $national );

		if ( $len < 7 || $len > 15 ) {
			return $this->reject(
				sprintf(
					/* translators: %s: the rejected raw value. */
					__( '"%s" is not a plausible phone number for the configured default country (wrong length).', 'rewind-form-bridge-for-brevo' ),
					$raw
				)
			);
		}

		return $this->accept( '+' . $dial . $national );
	}

	/**
	 * Success triple.
	 *
	 * @param string $value Normalised number.
	 * @return array
	 */
	private function accept( string $value ): array {
		return array(
			'ok'     => true,
			'value'  => $value,
			'reason' => '',
		);
	}

	/**
	 * Rejection triple.
	 *
	 * @param string $reason Log-worthy explanation.
	 * @return array
	 */
	private function reject( string $reason ): array {
		return array(
			'ok'     => false,
			'value'  => null,
			'reason' => $reason,
		);
	}
}
