<?php
/**
 * Structured destination dispatch result.
 *
 * @package Carticy\Conversions\Events
 */

declare(strict_types=1);

namespace Carticy\Conversions\Events;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Immutable result returned by destinations that need queue and diagnostic
 * statuses instead of a simple boolean.
 */
final class DispatchResult {

	public const STATUS_QUEUED                   = 'queued';
	public const STATUS_SENT                     = 'sent';
	public const STATUS_RETRY_SCHEDULED          = 'retry_scheduled';
	public const STATUS_BLOCKED_CONSENT          = 'blocked_consent';
	public const STATUS_BLOCKED_MISSING_CONFIG   = 'blocked_missing_config';
	public const STATUS_BLOCKED_MISSING_IDENTITY = 'blocked_missing_identity';
	public const STATUS_FAILED                   = 'failed';
	public const STATUS_PERMANENT_FAILED         = 'permanent_failed';

	/**
	 * Dispatch status.
	 *
	 * @var string
	 */
	private string $status;

	/**
	 * Whether the destination accepted ownership of the event.
	 *
	 * @var bool
	 */
	private bool $accepted;

	/**
	 * Redacted diagnostic details.
	 *
	 * @var array<string, mixed>
	 */
	private array $details;

	/**
	 * Constructor.
	 *
	 * @param string               $status Dispatch status.
	 * @param bool                 $accepted Whether the destination accepted ownership of the event.
	 * @param array<string, mixed> $details Redacted dispatch details.
	 */
	public function __construct( string $status, bool $accepted, array $details = array() ) {
		$this->status   = sanitize_key( $status );
		$this->accepted = $accepted;
		$this->details  = $this->sanitize_details( $details );
	}

	/**
	 * Build an accepted result.
	 *
	 * @param string               $status Dispatch status.
	 * @param array<string, mixed> $details Redacted dispatch details.
	 * @return self
	 */
	public static function accepted( string $status, array $details = array() ): self {
		return new self( $status, true, $details );
	}

	/**
	 * Build a blocked or failed result.
	 *
	 * @param string               $status Dispatch status.
	 * @param array<string, mixed> $details Redacted dispatch details.
	 * @return self
	 */
	public static function rejected( string $status, array $details = array() ): self {
		return new self( $status, false, $details );
	}

	/**
	 * Get dispatch status.
	 *
	 * @return string
	 */
	public function status(): string {
		return $this->status;
	}

	/**
	 * Whether the destination accepted ownership of the event.
	 *
	 * @return bool
	 */
	public function is_accepted(): bool {
		return $this->accepted;
	}

	/**
	 * Get redacted diagnostic details.
	 *
	 * @return array<string, mixed>
	 */
	public function details(): array {
		return $this->details;
	}

	/**
	 * Sanitize diagnostic details before storage.
	 *
	 * @param array<string, mixed> $details Raw details.
	 * @return array<string, mixed>
	 */
	private function sanitize_details( array $details ): array {
		$allowed = array(
			'event_id',
			'next_attempt_at',
			'last_attempt_at',
			'last_http_status',
			'last_error_code',
			'last_error_message',
			'response_summary',
			'payload_hash',
		);

		$sanitized = array();
		foreach ( $allowed as $key ) {
			if ( ! array_key_exists( $key, $details ) ) {
				continue;
			}

			if ( 'last_http_status' === $key ) {
				$sanitized[ $key ] = abs( (int) $details[ $key ] );
				continue;
			}

			$value             = is_scalar( $details[ $key ] ) ? (string) $details[ $key ] : wp_json_encode( $details[ $key ] );
			$sanitized[ $key ] = substr( trim( (string) $value ), 0, 500 );
		}

		return $sanitized;
	}
}
