<?php
/**
 * Transients-backed {@see ProviderHealthCache} adapter.
 *
 * Phase 9 MVP implementation of the
 * {@see \Affilun\Application\Cache\ProviderHealthCache}
 * port, composing a {@see TransientsCacheStore} so the WordPress
 * transient-name hashing, envelope serialization, and TTL
 * eviction live in exactly one place across the four Phase 9
 * cache layers.
 *
 * Spec: `specifications/sync-and-cache-strategy.md` §4 (cache
 * layers), §4.2 (Object Cache / Transients as L2), and §6.3
 * (cache-key forms).
 *
 * Unlike the search / lookup / render adapters, this layer
 * stores a typed Domain value object verbatim. WordPress
 * `set_transient()` runs `maybe_serialize()` on the stored
 * envelope, which serializes the contained
 * {@see ProviderHealthStatus} (immutable VO with primitive +
 * {@see \DateTimeImmutable} + array fields) without further
 * adapter work.
 *
 * Layer isolation is preserved by injecting a dedicated
 * {@see TransientsCacheStore} instance per layer; the wiring in
 * {@see \Affilun\Application\Cache\CacheServiceProvider}
 * follows that convention for symmetry with the in-memory
 * variants even though the transient backend has no per-layer
 * flush state to partition.
 *
 * @package Affilun\Infrastructure\Cache
 */

declare(strict_types=1);

namespace Affilun\Infrastructure\Cache;

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

/**
 * Transients-backed {@see ProviderHealthCache} adapter.
 *
 * See the file-level docblock for the typed-value semantics and
 * the layer-isolation convention.
 */
final class TransientsProviderHealthCache implements ProviderHealthCache {

	/**
	 * Backing generic transient store; carries the transient-name
	 * hashing, the envelope serialization, the clock, and the
	 * TTL eviction logic shared across cache layers.
	 */
	private TransientsCacheStore $store;

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

	/**
	 * Read the cached {@see ProviderHealthStatus}, or null on
	 * miss / expired / malformed envelope.
	 *
	 * @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 transient envelope were
			// tampered with; 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 TransientsCacheStore::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 );
	}

	/**
	 * Phase-9-MVP no-op (delegates to
	 * {@see TransientsCacheStore::flush()}, which documents why
	 * Transients cannot be enumerated without a `$wpdb`-backed
	 * sweep that this slice does not introduce).
	 */
	public function flush(): void {
		$this->store->flush();
	}
}
