<?php
/**
 * Registry-backed provider resolver.
 *
 * Phase 2 (post-MVP) Application-layer adapter that satisfies the
 * {@see ProviderResolver} contract by delegating lookups to a
 * {@see ProviderRegistry}. The Phase 12 slice 12c centralization of
 * provider construction made the registry the single canonical
 * source of registered {@see ProviderInterface} implementations;
 * this adapter bridges that registry to the {@see ProviderResolver}
 * port that {@see \CommerceLinkBuilder\Application\Sync\SyncProductJobHandler}
 * depends on so the Phase 2 sync worker / periodic-sync wiring can
 * reuse the production provider graph without standing up a parallel
 * resolver implementation.
 *
 * Responsibilities:
 *
 *   - Look the supplied provider key up in the registry. When the
 *     registry has no entry for the key, raise
 *     {@see UnknownProviderException} per the
 *     {@see ProviderResolver::resolveItemLookup()} contract.
 *
 *   - When the registry entry exists but does NOT implement
 *     {@see ItemLookupProviderInterface} (i.e. the registered
 *     implementation cannot satisfy the §6.3 single-item lookup
 *     contract the sync pipeline depends on), raise
 *     {@see UnknownProviderException} for the same reason — from
 *     the resolver-port perspective there is no item-lookup
 *     provider for the key, even if a different-shaped provider
 *     happens to be registered under it.
 *
 * Design notes:
 *
 *   - The adapter holds a reference to the registry rather than to
 *     a frozen snapshot of its `all()` map. The registry is itself
 *     immutable, so the reference cannot drift, but holding the
 *     registry keeps the adapter trivially compatible with a
 *     future registry that swaps providers in / out at runtime
 *     (for example a settings-change listener that rebuilds the
 *     registry without rebooting the worker).
 *
 *   - The adapter is a thin pass-through with no caching. The
 *     registry's own {@see ProviderRegistry::get()} / {@see
 *     ProviderRegistry::has()} surfaces are O(1) array lookups on
 *     a small map (three providers in the MVP), so a cache would
 *     add complexity without measurable benefit.
 *
 *   - The adapter is `final` and stateless beyond the registry
 *     reference. It carries no per-call state, so a single
 *     instance can be shared across the Phase 2 worker tick, the
 *     periodic-sync tick, and the Phase 11 REST sync controller
 *     without coordination.
 *
 * @package CommerceLinkBuilder\Application\Sync
 */

declare(strict_types=1);

namespace CommerceLinkBuilder\Application\Sync;

use CommerceLinkBuilder\Application\Provider\ProviderRegistry;
use CommerceLinkBuilder\Application\Sync\Exception\UnknownProviderException;
use CommerceLinkBuilder\Domain\Provider\ItemLookupProviderInterface;

/**
 * {@see ProviderResolver} adapter backed by a {@see ProviderRegistry}.
 *
 * See the file-level docblock for the lookup semantics, the
 * "registered but not item-lookup" failure mode, and the rationale
 * for holding the registry by reference rather than snapshotting
 * its contents.
 */
final class RegistryBackedProviderResolver implements ProviderResolver {

	/**
	 * Canonical registry of registered providers.
	 *
	 * Held by reference so a future registry rebuild (for example
	 * triggered by a settings-change listener) is observed by every
	 * subsequent {@see self::resolveItemLookup()} call without
	 * rebuilding this adapter.
	 */
	private ProviderRegistry $registry;

	/**
	 * Construct the adapter against the supplied registry.
	 *
	 * @param ProviderRegistry $registry Canonical provider registry
	 *                                   (typically resolved from
	 *                                   {@see \CommerceLinkBuilder\Presentation\Rest\ProviderServiceProvider::BINDING_PROVIDER_REGISTRY}).
	 */
	public function __construct( ProviderRegistry $registry ) {
		$this->registry = $registry;
	}

	/**
	 * Resolve the supplied provider key to its item-lookup-capable
	 * provider implementation.
	 *
	 * Lookup order:
	 *
	 *   1. If the registry has no entry for `$providerKey`, raise
	 *      {@see UnknownProviderException} per the resolver
	 *      contract.
	 *
	 *   2. If the registry entry exists but does not implement
	 *      {@see ItemLookupProviderInterface}, raise
	 *      {@see UnknownProviderException} for the same reason —
	 *      from the resolver-port perspective the key has no
	 *      item-lookup provider attached to it.
	 *
	 *   3. Otherwise return the matched provider.
	 *
	 * @param string $providerKey §4 stable provider key.
	 *
	 * @throws UnknownProviderException When no registered provider
	 *                                  matches the key, or when the
	 *                                  matched provider does not
	 *                                  implement
	 *                                  {@see ItemLookupProviderInterface}.
	 */
	public function resolveItemLookup( string $providerKey ): ItemLookupProviderInterface {
		if ( ! $this->registry->has( $providerKey ) ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw UnknownProviderException::forKey( $providerKey );
		}

		$provider = $this->registry->get( $providerKey );

		if ( ! $provider instanceof ItemLookupProviderInterface ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw UnknownProviderException::forKey( $providerKey );
		}

		return $provider;
	}

	/**
	 * Underlying registry the adapter resolves against.
	 *
	 * Exposed so tests and diagnostics can probe the canonical
	 * registry without re-resolving the binding from the service
	 * container.
	 */
	public function registry(): ProviderRegistry {
		return $this->registry;
	}
}
