<?php
/**
 * In-memory {@see ProviderHealthCache} adapter.
 *
 * Phase 9 in-memory implementation of the
 * {@see \Affilun\Application\Cache\ProviderHealthCache}
 * port, composing a shared {@see InMemoryCacheStore} backend so
 * the eviction / flush / TTL machinery is implemented exactly
 * once across the four Phase 9 cache layers.
 *
 * Spec: `specifications/sync-and-cache-strategy.md` §4 (cache
 * layers) and §6.3 (cache-key forms).
 *
 * Unlike the search / lookup / render adapters, this layer
 * stores a typed Domain value object verbatim — callers receive
 * the exact {@see ProviderHealthStatus} instance back on hit, so
 * downstream surfaces can branch on the §7.10 status tokens
 * without re-parsing an associative array.
 *
 * The adapter is layer-isolated: callers are expected to inject a
 * dedicated {@see InMemoryCacheStore} instance per layer so a
 * flush of the provider-health layer does not also drop entries
 * belonging to the search, lookup, or render layers. Sharing a
 * single store across multiple adapters is permitted but is the
 * caller's responsibility.
 *
 * @package Affilun\Infrastructure\Cache
 */

declare(strict_types=1);

namespace Affilun\Infrastructure\Cache;

use Affilun\Application\Cache\ProviderHealthCache;
use Affilun\Domain\Provider\ProviderHealthStatus;

/**
 * In-memory {@see ProviderHealthCache} backed by
 * {@see InMemoryCacheStore}.
 *
 * See the file-level docblock for the typed-value semantics and
 * the layer-isolation rule.
 */
final class InMemoryProviderHealthCache implements ProviderHealthCache {

	/**
	 * Backing generic store; carries the entries, the clock, and
	 * the TTL eviction logic shared across cache layers.
	 */
	private InMemoryCacheStore $store;

	/**
	 * Construct the adapter.
	 *
	 * @param InMemoryCacheStore $store Backing in-memory store.
	 */
	public function __construct( InMemoryCacheStore $store ) {
		$this->store = $store;
	}

	/**
	 * Read the cached {@see ProviderHealthStatus}, or null on
	 * miss / expired.
	 *
	 * @param string $key Cache key.
	 */
	public function get( string $key ): ?ProviderHealthStatus {
		$entry = $this->store->getRaw( $key );
		if ( null === $entry ) {
			return null;
		}

		$value = $entry->value();
		if ( ! $value instanceof ProviderHealthStatus ) {
			// Defensive: a non-ProviderHealthStatus value would only
			// occur if the underlying store were misused by another
			// caller; treat it as a miss rather than surfacing a
			// typed payload that violates the
			// ProviderHealthCache::get() contract.
			return null;
		}

		return $value;
	}

	/**
	 * Write the {@see ProviderHealthStatus} with the given TTL in
	 * seconds. Delegates TTL validation to
	 * {@see InMemoryCacheStore::putRaw()}.
	 *
	 * @param string               $key        Cache key.
	 * @param ProviderHealthStatus $status     Health snapshot to store.
	 * @param int                  $ttlSeconds TTL in seconds; must be > 0.
	 */
	public function put( string $key, ProviderHealthStatus $status, int $ttlSeconds ): void {
		$this->store->putRaw( $key, $status, $ttlSeconds );
	}

	/**
	 * Remove the entry stored under `$key`. Idempotent.
	 *
	 * @param string $key Cache key.
	 */
	public function forget( string $key ): void {
		$this->store->forget( $key );
	}

	/**
	 * Drop every entry stored in this layer's backing store.
	 */
	public function flush(): void {
		$this->store->flush();
	}
}
