<?php
/**
 * HTTP client port contract.
 *
 * Narrow Domain-layer port that brackets a single outbound HTTP call:
 * a caller hands an {@see HttpRequest} value to an implementation and
 * receives an {@see HttpResponse} value back. The contract is
 * deliberately limited to the single {@see self::send()} surface so
 * the production implementation
 * ({@see \Affilun\Infrastructure\Http\WpRemoteHttpClient})
 * and the in-memory test double
 * ({@see \Affilun\Tests\Unit\Infrastructure\Http\Fixtures\RecordingHttpClient}
 * once Phase 4 lands its fixtures) stay small, auditable, and
 * substitutable.
 *
 * Design notes:
 *
 *   - The contract speaks in {@see HttpRequest} / {@see HttpResponse}
 *     value objects rather than driver-specific shapes (`wp_remote_*`
 *     associative arrays, PSR-7 message objects, raw cURL handles).
 *     This keeps the provider layer driver-agnostic and lets concrete
 *     adapters translate to their native call shape on the boundary.
 *
 *   - The port lives in the Domain layer per
 *     `specifications/coding-standards.md` §1.3 B because the Phase 4
 *     provider implementations consume it directly. The WordPress-
 *     dependent implementation lives in `Infrastructure/Http/`; pure-
 *     PHP test doubles live alongside the test suite. Domain code
 *     therefore depends only on the interface, never on `wp_remote_*`
 *     symbols.
 *
 *   - {@see self::send()} is synchronous. Concurrent / batched
 *     dispatch is not part of the Phase 4 contract because the
 *     existing provider workflows (single search, single lookup,
 *     single affiliate-link build) do not need it. Future provider
 *     implementations that want to issue parallel calls can extend
 *     the port with a `sendMany()` method as a deliberate code change.
 *
 *   - Implementations MUST forward the {@see HttpRequest::method()},
 *     {@see HttpRequest::url()}, headers, query parameters, body, and
 *     timeout verbatim. Adapters that need to stamp default headers
 *     (`User-Agent`, `Accept`) SHOULD do so via
 *     {@see HttpRequest::withHeader()} on a copy before dispatch so
 *     the supplied request stays the canonical "what the caller
 *     asked for" record — useful for log lines, retry decisions, and
 *     the diagnostics surface.
 *
 * Failure semantics:
 *
 *   - Transport-level failures (DNS, TCP, TLS, response read errors)
 *     MUST raise {@see HttpClientException} carrying the originating
 *     {@see HttpRequest}. The "no usable {@see HttpResponse} could be
 *     produced" boundary is the canonical separator between this
 *     port's failure surface and the provider layer's §10.2 error
 *     contract.
 *
 *   - Application-level failures (4xx / 5xx status codes, malformed
 *     bodies, contract drift) are NOT transport failures and MUST
 *     NOT surface as {@see HttpClientException}. Implementations
 *     return a fully-populated {@see HttpResponse} carrying the
 *     non-2xx status; the provider error mapper translates from
 *     there into one of the §10.2
 *     {@see \Affilun\Domain\Provider\Exception\ProviderException}
 *     subclasses.
 *
 *   - Per `provider-api-contracts.md` §10.1, neither the
 *     {@see HttpClientException} message body nor the headers /
 *     query parameters preserved on the carried {@see HttpRequest}
 *     may contain secret material. Adapters that stamp credentials
 *     onto the request before dispatch SHOULD redact them out of
 *     the request copy before constructing the exception when the
 *     failure path will surface to logs.
 *
 * @package Affilun\Domain\Http
 */

declare(strict_types=1);

namespace Affilun\Domain\Http;

/**
 * Transport-layer port for issuing a single outbound HTTP call.
 *
 * See the file-level docblock for the driver-agnostic value-object
 * contract, the synchronous-only Phase 4 scope, and the boundary
 * between transport failures (this port) and application-level
 * failures (the provider layer's §10.2 error contract).
 */
interface HttpClient {

	/**
	 * Dispatch the supplied request and return the resulting
	 * response.
	 *
	 * Lifecycle:
	 *
	 *   1. Translate the {@see HttpRequest} into the implementation's
	 *      native call shape, forwarding method / URL / headers /
	 *      query parameters / body / timeout verbatim.
	 *
	 *   2. Issue the call synchronously and wait for the upstream's
	 *      response.
	 *
	 *   3. On a successful round-trip — even when the upstream
	 *      returned a non-2xx status — translate the native response
	 *      shape into an {@see HttpResponse} and return it. The
	 *      caller is responsible for branching on
	 *      {@see HttpResponse::statusCode()} to decide whether the
	 *      application-level outcome was acceptable.
	 *
	 *   4. On any transport-level failure — DNS resolution failure,
	 *      TCP connection refused, TLS handshake failure, request
	 *      body could not be transmitted, response could not be
	 *      read, driver-internal error before a status code was
	 *      available — raise {@see HttpClientException} carrying
	 *      the supplied {@see HttpRequest} so observability surfaces
	 *      and provider error mappers can correlate the failure
	 *      with the originating request.
	 *
	 * Implementations MUST NOT raise {@see HttpClientException} for
	 * application-level failures (4xx / 5xx status codes, malformed
	 * bodies). Those cases produce a fully-populated
	 * {@see HttpResponse}; translation to a §10.2 provider exception
	 * happens in the provider error mapper, not here.
	 *
	 * Implementations MUST execute the request exactly once per
	 * call. Retry / backoff policy is the caller's concern (typically
	 * delegated to the provider layer) so this port stays free of
	 * scheduling state.
	 *
	 * @param HttpRequest $request Request to dispatch.
	 *
	 * @return HttpResponse Response produced by the upstream,
	 *                      regardless of status code.
	 *
	 * @throws HttpClientException When the call fails at the
	 *                             transport layer and no usable
	 *                             {@see HttpResponse} can be
	 *                             produced.
	 */
	public function send( HttpRequest $request ): HttpResponse;
}
