<?php
/**
 * HTTP client transport exception.
 *
 * Raised by {@see HttpClient} implementations when an outbound HTTP
 * call fails at the transport layer — DNS resolution failure, TCP
 * connection refused, TLS handshake failure, request body could not
 * be transmitted, response could not be read, or the underlying
 * driver itself threw an unexpected error before a status code was
 * available.
 *
 * Field semantics:
 *
 *   - The exception is deliberately scoped to "no usable
 *     {@see HttpResponse} could be produced". Cases where the
 *     upstream returned a syntactically-valid response that the
 *     application layer dislikes (4xx / 5xx status codes, malformed
 *     bodies, contract drift) are NOT transport failures and MUST
 *     NOT surface as this exception — they are the provider error
 *     mapper's responsibility to translate into one of the §10.2
 *     {@see \Affilun\Domain\Provider\Exception\ProviderException}
 *     subclasses.
 *
 *   - The exception carries the {@see HttpRequest} that was being
 *     dispatched when the failure occurred so observability / log
 *     surfaces can correlate the failure with the originating
 *     request without consulting separate sibling state. The DTO
 *     is held by reference rather than rendered into a string
 *     because consumers often need structured access to the
 *     headers / query parameters when triaging.
 *
 *   - Per the §10.1 secret-material rule that binds every Domain
 *     exception, callers MUST NOT pass credentials, signing keys,
 *     or unredacted authorization headers in the message body.
 *     The {@see HttpRequest} this exception carries is also
 *     subject to that rule — provider implementations that stamp
 *     credentials onto the request via
 *     {@see HttpRequest::withHeader()} should redact them out of
 *     the request before constructing this exception when the
 *     failure path will surface to logs.
 *
 * Inheritance:
 *
 *   - Extends {@see \RuntimeException} (not {@see \LogicException})
 *     because every transport failure represents an externally-
 *     induced failure mode (network, OS, driver) rather than a
 *     programming error the static type system could have caught.
 *
 *   - Distinct from {@see \Affilun\Domain\Provider\Exception\ProviderException}:
 *     {@see HttpClient} is the transport-layer port and its
 *     failures are naturally below the provider abstraction.
 *     Provider implementations that catch this exception are
 *     expected to translate it into the matching §10.2 subclass
 *     (typically
 *     {@see \Affilun\Domain\Provider\Exception\TemporaryUnavailableException}
 *     for retriable transport failures) before re-raising.
 *
 * @package Affilun\Domain\Http
 */

declare(strict_types=1);

namespace Affilun\Domain\Http;

use RuntimeException;
use Throwable;

/**
 * Transport-level failure raised by {@see HttpClient} implementations
 * when no usable {@see HttpResponse} could be produced.
 *
 * See the file-level docblock for the boundary between transport
 * failures and provider-layer response-shape failures, and for the
 * secret-material rule that binds the message body and the
 * preserved {@see HttpRequest}.
 */
final class HttpClientException extends RuntimeException {

	/**
	 * The request that was being dispatched when the transport
	 * failure occurred.
	 *
	 * Held by reference so observability / log surfaces can
	 * correlate the failure with the originating request without
	 * consulting separate sibling state. Per the §10.1 secret-
	 * material rule, callers SHOULD redact credential headers
	 * before constructing this exception when the failure path
	 * will surface to logs.
	 */
	private HttpRequest $request;

	/**
	 * Construct a transport-level failure for the given request.
	 *
	 * The constructor preserves the request reference and forwards
	 * `message`, `code`, and `previous` to the {@see RuntimeException}
	 * base verbatim. No trimming or shape validation is performed
	 * on the message body — it is the caller's responsibility to
	 * supply a non-secret summary, per the §10.1 rule.
	 *
	 * @param HttpRequest    $request  Request that was being dispatched.
	 * @param string         $message  Human-readable, non-secret summary.
	 * @param int            $code     Driver-specific error code, or 0.
	 * @param Throwable|null $previous Underlying transport-layer cause,
	 *                                 or null when the failure was
	 *                                 detected by the adapter without
	 *                                 a wrapping exception.
	 */
	public function __construct(
		HttpRequest $request,
		string $message = '',
		int $code = 0,
		?Throwable $previous = null
	) {
		$this->request = $request;

		parent::__construct( $message, $code, $previous );
	}

	/**
	 * Request that was being dispatched when the transport failure
	 * occurred.
	 *
	 * Exposed so observability / log surfaces and provider error
	 * mappers can branch on the originating request's method,
	 * URL, and headers without parsing the human-readable message
	 * body.
	 */
	public function request(): HttpRequest {
		return $this->request;
	}
}
