<?php
/**
 * Unsupported-feature provider exception.
 *
 * Concrete {@see ProviderException} subclass that signals the caller
 * asked the provider implementation to perform an operation that the
 * provider does not support. Typical examples are calling a search
 * surface on a lookup-only provider, asking for a sort order the
 * provider's search API cannot honor, or requesting an affiliate-link
 * build for a provider whose terms do not include an affiliate
 * program.
 *
 * Field semantics match `specifications/provider-api-contracts.md`
 * §10.2 / §7.9 exactly:
 *
 *   - {@see self::ERROR_CODE} is `unsupported_feature`, 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 §7.9 / §8 capabilities surface
 *     ({@see \Affilun\Domain\Provider\ProviderCapabilities})
 *     is the canonical "what does this provider support?" answer;
 *     consuming surfaces SHOULD consult it before issuing a request
 *     so this exception is reserved for the genuinely-unexpected
 *     mismatch (caller bypassed the capabilities check, capabilities
 *     drifted between probe time and call time, or a provider
 *     implementation declared a feature it later cannot honor for a
 *     specific input).
 *
 *   - 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 implementation receives a search call but does not
 *     implement
 *     {@see \Affilun\Domain\Provider\SearchProviderInterface};
 *     the dispatch layer raises this exception rather than fanning
 *     out to a no-op.
 *
 *   - Provider implementation receives a
 *     {@see \Affilun\Domain\Search\SearchQuery} whose
 *     `sort` discriminator is one this provider does not honor at
 *     all (rather than a sort whose specific value is invalid,
 *     which would be a {@see ProviderRequestException}).
 *
 *   - Application layer asks the provider to mint an affiliate URL
 *     but
 *     {@see \Affilun\Domain\Provider\ProviderCapabilities::supportsAffiliateLinkBuild()}
 *     reports false; raising here lets the caller fall back to the
 *     plain detail URL without ambiguity.
 *
 * Distinct from {@see ProviderRequestException}: that exception
 * fires when the requested feature is supported but the specific
 * parameters supplied are invalid for it; this exception fires when
 * the requested feature is not supported at all by this provider.
 *
 * Distinct from {@see ProviderConfigurationException}: that
 * exception fires when the provider would in principle support the
 * feature but its own configuration is incomplete; this exception
 * fires when the provider has a structural inability to support the
 * feature regardless of configuration.
 *
 * @package Affilun\Domain\Provider\Exception
 */

declare(strict_types=1);

namespace Affilun\Domain\Provider\Exception;

use Throwable;

/**
 * Provider exception for operations the provider implementation
 * does not support per its declared
 * {@see \Affilun\Domain\Provider\ProviderCapabilities}.
 *
 * See the file-level docblock for the mapping to
 * `provider-api-contracts.md` §10.2 / §7.9, the typical raise
 * sites, and the distinctions versus
 * {@see ProviderRequestException} and
 * {@see ProviderConfigurationException}.
 */
final class UnsupportedFeatureException 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 = 'unsupported_feature';

	/**
	 * Construct an unsupported-feature exception.
	 *
	 * Forwards {@see self::ERROR_CODE} to the
	 * {@see ProviderException} base so every unsupported-feature
	 * 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.
	 *
	 * Provider implementations SHOULD populate the `context` map
	 * with a `feature` entry naming the §8 feature key (or a
	 * provider-specific operation name when no §8 key applies) so
	 * consuming surfaces can surface a precise "this provider does
	 * not support {feature}" admin message without parsing 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;
	 *                                       SHOULD include a `feature`
	 *                                       entry naming the unsupported
	 *                                       operation.
	 * @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 );
	}
}
