<?php
/**
 * The public PHP API other plugins and themes may call.
 *
 * @package TextTango
 */

declare( strict_types=1 );

use TextTango\Api\Client;
use TextTango\Api\PhoneNumber;
use TextTango\Sending\Sender;
use TextTango\Support\Settings;

defined( 'ABSPATH' ) || exit;

/*
 * Everything below is a supported, stable surface (spec §18). The rest of the
 * plugin is not: classes may be renamed, split or removed between versions, and
 * anything reaching past these functions is on its own.
 *
 * Each is guarded by function_exists so a site that already defines the name —
 * or loads two copies of this plugin during an upgrade — fatals on neither.
 *
 * Everything is deliberately callable at any time, including before the plugin
 * has booted, because a caller cannot be expected to know this plugin's load
 * order. Anything unavailable is reported as a WP_Error rather than assumed.
 */

if ( ! function_exists( 'texttango_send_sms' ) ) {
	/**
	 * Send one SMS.
	 *
	 * Goes through the same path as everything else this plugin sends, so the
	 * consent gate, the opt-out list, sandbox mode, deduplication and the delivery
	 * log all apply exactly as they do to an order notification. A caller cannot
	 * accidentally bypass them, which is the point of there being an API at all
	 * rather than an exposed HTTP client.
	 *
	 * ### On the return value
	 *
	 * Test with `is_wp_error()`, never for truthiness. A message sent while
	 * sandbox mode is on succeeds but has **no UUID**, because nothing was handed
	 * to the platform — so the return is an empty string, and treating that as a
	 * failure would make every staging site look broken.
	 *
	 * @since 0.2.0
	 *
	 * @param string               $to      Recipient. Local formats are accepted and
	 *                                      normalized against the site's default country.
	 * @param string               $message Body to send.
	 * @param array<string, mixed> $args    {
	 *     Optional.
	 *
	 *     @type string   $event      Event name recorded in the log. Defaults to `api`.
	 *                                Names starting `marketing_` or `campaign_` require
	 *                                marketing consent; anything else does not.
	 *     @type string   $dedupe_key Idempotency key. Sending twice with the same key
	 *                                sends once. Defaults to a unique value, meaning
	 *                                no deduplication.
	 *     @type string   $country    ISO 3166-1 alpha-2 hint for normalizing `$to`.
	 *     @type string   $from       Sender ID. Defaults to the site's.
	 *     @type int|null $order_id   WooCommerce order to associate and annotate.
	 *     @type int|null $wp_user_id User to associate, and whose consent is checked.
	 * }
	 * @return string|WP_Error The TextTango message UUID, an empty string when
	 *                         sandboxed, or a WP_Error explaining why nothing was sent.
	 */
	function texttango_send_sms( string $to, string $message, array $args = array() ) {
		if ( ! texttango_is_connected() ) {
			return new WP_Error(
				'texttango_not_connected',
				__( 'TextTango is not connected, so no message was sent.', 'texttango' )
			);
		}

		$result = Sender::send(
			array_merge(
				array( 'event' => 'api' ),
				// Filtered rather than merged wholesale. See Sender::public_args():
				// a caller must not be able to send for real from a staging site, or
				// choose what the delivery log remembers about what it sent.
				Sender::public_args( $args ),
				array(
					'to'      => $to,
					'message' => $message,
				)
			)
		);

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		return (string) ( $result['uuid'] ?? '' );
	}
}

if ( ! function_exists( 'texttango_is_connected' ) ) {
	/**
	 * Whether this site holds credentials it can actually read.
	 *
	 * Local only — it makes no request, so it is cheap enough to call on every
	 * page load. It answers "is this configured", not "is TextTango reachable";
	 * a stored token can still be revoked or out of credit.
	 *
	 * @since 0.2.0
	 *
	 * @return bool
	 */
	function texttango_is_connected(): bool {
		if ( ! class_exists( Settings::class ) ) {
			return false;
		}

		return '' !== Settings::get_api_key();
	}
}

if ( ! function_exists( 'texttango_get_balance' ) ) {
	/**
	 * The account balance.
	 *
	 * Makes a live request, so do not call it on an uncached page render.
	 *
	 * @since 0.2.0
	 *
	 * @return array{amount: float, currency: string, formatted: string}|WP_Error
	 */
	function texttango_get_balance() {
		$client = Client::from_settings();

		if ( is_wp_error( $client ) ) {
			return $client;
		}

		$response = $client->wallet();

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$wallet = TextTango\Admin\Ajax::parse_wallet( (array) $response );

		return array(
			'amount'    => (float) $wallet['main'],
			'currency'  => (string) $wallet['currency'],
			'formatted' => (string) $wallet['formatted'],
		);
	}
}

if ( ! function_exists( 'texttango_normalize_phone' ) ) {
	/**
	 * Turn a number into E.164.
	 *
	 * Useful on its own for validating a field before storing it, so a bad number
	 * is caught while somebody is still looking at the form rather than months
	 * later when a message fails to send.
	 *
	 * @since 0.2.0
	 *
	 * @param string      $raw     Number in any format.
	 * @param string|null $country ISO 3166-1 alpha-2 hint. Defaults to the site setting.
	 * @return string|WP_Error E.164 number, or an error naming what is wrong with it.
	 */
	function texttango_normalize_phone( string $raw, ?string $country = null ) {
		return PhoneNumber::normalize( $raw, $country );
	}
}
