<?php
// DESCRIPTION: Wraps Google Sheets API calls with retry logic, exponential backoff,
// and error classification to prevent silent data loss on transient failures.

namespace SheetWise\Sync;

class RetryHandler {

	/**
	 * Execute a callable with retry logic
	 *
	 * Retries on transient failures with exponential backoff.
	 * Immediately fails on permanent errors.
	 *
	 * @since 2.0.0
	 *
	 * @param callable $api_call The Google Sheets API call to execute
	 * @param array    $context  Context for logging (integration_id, hook, data_payload, etc.)
	 *
	 * @return array {
	 *     @type string $status   'success', 'failed', or 'dead_lettered'
	 *     @type mixed  $response The API response on success, null on failure
	 *     @type array  $error    Error details from ErrorClassifier on failure, null on success
	 *     @type int    $attempts Number of attempts made
	 * }
	 */
	public function execute( callable $api_call, array $context = [] ) {
		$settings     = swise_get_retry_settings();
		$max_retries  = $settings['max_retries'];
		$attempts     = 0;
		$last_error   = null;

		for ( $attempt = 0; $attempt <= $max_retries; $attempt++ ) {
			$attempts++;

			try {
				$response = call_user_func( $api_call );

				return [
					'status'   => 'success',
					'response' => $response,
					'error'    => null,
					'attempts' => $attempts,
				];
			} catch ( \Exception $e ) {
				$last_error = $e;

				// Permanent errors should not be retried
				if ( ErrorClassifier::is_permanent( $e ) ) {
					break;
				}

				// Transient errors get retried with backoff
				if ( ErrorClassifier::is_transient( $e ) && $attempt < $max_retries ) {
					$delay = $this->calculate_delay( $attempt, $settings );
					sleep( $delay );
					continue;
				}

				// Unknown errors or exhausted retries
				break;
			}
		}

		// Determine final status
		$error_details = ErrorClassifier::get_error_details( $last_error );
		$status        = 'dead_lettered';

		if ( ErrorClassifier::is_permanent( $last_error ) ) {
			$status = 'failed';
		}

		$result = [
			'status'   => $status,
			'response' => null,
			'error'    => $error_details,
			'attempts' => $attempts,
		];

		if ( $status === 'dead_lettered' ) {
			/**
			 * Fires when a sync is dead-lettered after exhausting all retries
			 *
			 * @since 2.0.0
			 *
			 * @param array $context Sync context including integration_id, hook, data_payload
			 * @param array $result  Result array with error details and attempt count
			 */
			do_action( 'swise_sync_dead_lettered', $context, $result );
		}

		return $result;
	}

	/**
	 * Calculate the delay before the next retry attempt using exponential backoff with jitter
	 *
	 * @since 2.0.0
	 *
	 * @param int   $attempt  The current attempt number (0-based)
	 * @param array $settings Retry settings with base_delay and max_delay
	 *
	 * @return int Delay in seconds
	 */
	private function calculate_delay( $attempt, array $settings ) {
		$base_delay = $settings['base_delay'];
		$max_delay  = $settings['max_delay'];

		// Exponential backoff: base * 2^attempt
		$delay = $base_delay * pow( 2, $attempt );

		// Add jitter (±25%) to prevent thundering herd
		$jitter = $delay * 0.25;
		$delay  = $delay + wp_rand( (int) ( -$jitter * 100 ), (int) ( $jitter * 100 ) ) / 100;

		return min( (int) max( $delay, 1 ), $max_delay );
	}
}
