<?php
/**
 * Phone number normalization.
 *
 * @package TextTango
 */

declare( strict_types=1 );

namespace TextTango\Api;

use TextTango\Support\Settings;
use TextTango\Vendor\libphonenumber\NumberParseException;
use TextTango\Vendor\libphonenumber\PhoneNumberFormat;
use TextTango\Vendor\libphonenumber\PhoneNumberUtil;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Turns whatever a customer typed into E.164 (spec §20).
 *
 * Normalizing before every send is what keeps `INVALID_PHONE_NUMBER` skips down
 * (spec §15): a Ghanaian customer who enters `024 412 3456` should still receive
 * their order updates, and an unusable number should be recognised here — for
 * free — rather than by paying for a rejected send.
 *
 * Backed by libphonenumber, vendored under the plugin's own namespace so it can
 * never clash with another plugin's copy.
 */
final class PhoneNumber {

	/**
	 * Fallback country when neither the caller nor the settings supply one.
	 */
	public const FALLBACK_COUNTRY = 'GH';

	/**
	 * Normalize a number to E.164.
	 *
	 * @param string      $raw     Number as entered.
	 * @param string|null $country ISO 3166-1 alpha-2 country to interpret local
	 *                             numbers against; defaults to the site setting.
	 * @return string|WP_Error E.164 number, or an error describing why it is unusable.
	 */
	public static function normalize( string $raw, ?string $country = null ) {
		$raw     = trim( $raw );
		$country = self::resolve_country( $country );

		if ( '' === $raw ) {
			return new WP_Error(
				'texttango_invalid_phone',
				__( 'No phone number was provided.', 'texttango' ),
				array( 'raw' => $raw )
			);
		}

		try {
			$util   = PhoneNumberUtil::getInstance();
			$parsed = $util->parse( $raw, $country );
		} catch ( NumberParseException $e ) {
			return new WP_Error(
				'texttango_invalid_phone',
				__( 'That does not look like a phone number.', 'texttango' ),
				array(
					'raw'     => $raw,
					'country' => $country,
				)
			);
		}

		if ( ! $util->isValidNumber( $parsed ) ) {
			return new WP_Error(
				'texttango_invalid_phone',
				sprintf(
					/* translators: 1: the number as entered, 2: ISO country code. */
					__( '"%1$s" is not a valid phone number for %2$s.', 'texttango' ),
					$raw,
					$country
				),
				array(
					'raw'     => $raw,
					'country' => $country,
				)
			);
		}

		$e164 = $util->format( $parsed, PhoneNumberFormat::E164 );

		/**
		 * Filters the normalized E.164 number.
		 *
		 * @param string $e164    Normalized number.
		 * @param string $raw     Number as entered.
		 * @param string $country ISO country used to interpret it.
		 */
		return (string) apply_filters( 'texttango_normalize_phone', $e164, $raw, $country );
	}

	/**
	 * Whether a number can be normalized.
	 *
	 * @param string      $raw     Number as entered.
	 * @param string|null $country ISO country.
	 */
	public static function is_valid( string $raw, ?string $country = null ): bool {
		return ! is_wp_error( self::normalize( $raw, $country ) );
	}

	/**
	 * Format a number for display, hiding all but the last four digits.
	 *
	 * Used in logs and admin copy so a delivery record never has to show a full
	 * customer number (spec §13, PII minimisation).
	 *
	 * @param string $e164 Number in E.164.
	 */
	public static function mask( string $e164 ): string {
		$length = strlen( $e164 );

		if ( $length <= 4 ) {
			return str_repeat( '•', $length );
		}

		return str_repeat( '•', $length - 4 ) . substr( $e164, -4 );
	}

	/**
	 * The country used when the caller does not name one.
	 *
	 * @param string|null $country Caller-supplied country, if any.
	 */
	public static function resolve_country( ?string $country ): string {
		$country = strtoupper( trim( (string) $country ) );

		if ( 2 === strlen( $country ) ) {
			return $country;
		}

		$configured = strtoupper( trim( (string) Settings::get( 'default_country', self::FALLBACK_COUNTRY ) ) );

		return 2 === strlen( $configured ) ? $configured : self::FALLBACK_COUNTRY;
	}
}
