<?php
/**
 * Temporary-unavailable provider exception.
 *
 * Concrete {@see ProviderException} subclass that signals the
 * upstream provider is currently unable to serve the request due to
 * a transient failure where retry is reasonable but no specific
 * retry-after window is known. Typical examples are 5xx server-side
 * outages, network timeouts that the provider's HTTP client gave up
 * on after the configured retry budget, and gateway-level errors
 * that hint at upstream degradation rather than a permanent
 * rejection.
 *
 * Field semantics match `specifications/provider-api-contracts.md`
 * §10.2 / §10.3 exactly:
 *
 *   - {@see self::ERROR_CODE} is `temporary_unavailable`, the stable
 *     §10.2 code that REST error envelopes, log lines, and admin
 *     diagnostics views surface as the machine-readable identifier
 *     for this failure mode.
 *
 *   - The §10.3 admin-facing translation is "一時的に接続できま
 *     せん" — consuming surfaces are expected to map the error code
 *     to that human-readable string rather than rendering the raw
 *     exception message, which may carry provider-specific
 *     diagnostic detail unsuitable for end-users.
 *
 *   - Per §10.1 the exception message MUST NOT carry secret
 *     material; the provider implementation that constructs the
 *     exception is responsible for redacting before passing the
 *     summary in.
 *
 * Typical raise sites:
 *
 *   - Provider HTTP client receives a 5xx response (502 / 503 / 504)
 *     and the response body / headers do not indicate a permanent
 *     condition. The application layer is expected to back off and
 *     retry the request after a provider-appropriate delay.
 *   - Provider HTTP client times out at the network layer (DNS,
 *     TCP, TLS handshake) after exhausting its own internal retry
 *     budget. Raising here lets the application layer decide
 *     whether to fall back to a cached snapshot or re-queue the
 *     work for a later sync pass.
 *   - Upstream API returns a structured "service degraded" envelope
 *     that the provider implementation classifies as transient
 *     rather than as a contract drift (which would be a
 *     {@see ProviderResponseException} instead).
 *
 * Distinct from {@see RateLimitExceededException}: that exception
 * fires specifically for rate-limit refusals where a retry-after
 * hint is the canonical recovery signal; this exception fires for
 * generic transient outages where retry is possible but no
 * specific window is known. The
 * {@see \Affilun\Domain\Provider\ProviderHealthStatus}
 * surfaces the corresponding distinction as `STATUS_RATE_LIMITED`
 * versus `STATUS_UNREACHABLE` / `STATUS_DEGRADED`.
 *
 * Distinct from {@see ProviderResponseException}: that exception
 * fires when the upstream returned a syntactically-valid response
 * whose contents violate the cross-provider contract (missing
 * required fields, malformed JSON, contract drift); this exception
 * fires when the upstream did not return a usable response at all.
 *
 * @package Affilun\Domain\Provider\Exception
 */

declare(strict_types=1);

namespace Affilun\Domain\Provider\Exception;

use Throwable;

/**
 * Provider exception for transient upstream outages and network-
 * level failures where retry is reasonable.
 *
 * See the file-level docblock for the mapping to
 * `provider-api-contracts.md` §10.2 / §10.3, the typical raise
 * sites, and the distinctions versus
 * {@see RateLimitExceededException} and
 * {@see ProviderResponseException}.
 */
final class TemporaryUnavailableException extends ProviderException {

	/**
	 * Stable §10.2 error code for this failure mode.
	 *
	 * Exposed as a class constant so call sites can reference the
	 * exact token without duplicating the literal, and so a future
	 * spec change has a single location to update.
	 */
	public const ERROR_CODE = 'temporary_unavailable';

	/**
	 * Construct a temporary-unavailable exception.
	 *
	 * Forwards {@see self::ERROR_CODE} to the
	 * {@see ProviderException} base so every transient-failure
	 * raise carries the same §10.2 code regardless of which
	 * provider implementation surfaced it. The remaining
	 * cross-cutting fields (`provider`, `message`, `context`,
	 * `previous`) follow the base's contract verbatim — see
	 * {@see ProviderException} for the per-field validation rules
	 * and the secret-material rule that binds the message body.
	 *
	 * @param string               $provider §4 provider key that raised the exception.
	 * @param string               $message  Human-readable, non-secret summary.
	 * @param array<string, mixed> $context  Structured diagnostic context.
	 * @param Throwable|null       $previous Underlying cause, or null.
	 */
	public function __construct(
		string $provider,
		string $message = '',
		array $context = array(),
		?Throwable $previous = null
	) {
		parent::__construct( self::ERROR_CODE, $provider, $message, $context, $previous );
	}
}
