<?php
/**
 * In-memory {@see SearchCache} adapter.
 *
 * Phase 9 in-memory implementation of the
 * {@see \CommerceLinkBuilder\Application\Cache\SearchCache} 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).
 *
 * The adapter is layer-isolated: callers are expected to inject a
 * dedicated {@see InMemoryCacheStore} instance per layer so a
 * flush of the search layer does not also drop entries belonging
 * to the lookup, render, or provider-health layers. Sharing a
 * single store across multiple adapters is permitted but is the
 * caller's responsibility.
 *
 * @package CommerceLinkBuilder\Infrastructure\Cache
 */

declare(strict_types=1);

namespace CommerceLinkBuilder\Infrastructure\Cache;

use CommerceLinkBuilder\Application\Cache\SearchCache;

/**
 * In-memory {@see SearchCache} backed by {@see InMemoryCacheStore}.
 *
 * See the file-level docblock for the layer-isolation rule and
 * the spec citations that describe the cache-key forms this
 * adapter consumes.
 */
final class InMemorySearchCache implements SearchCache {

	/**
	 * 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 search-result payload, or null on miss /
	 * expired.
	 *
	 * @param string $key Cache key.
	 *
	 * @return array<string, mixed>|null
	 */
	public function get( string $key ): ?array {
		$entry = $this->store->getRaw( $key );
		if ( null === $entry ) {
			return null;
		}

		$value = $entry->value();
		if ( ! is_array( $value ) ) {
			// Defensive: a non-array 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 SearchCache::get() contract.
			return null;
		}

		/** @var array<string, mixed> $value */
		return $value;
	}

	/**
	 * Write the search-result payload with the given TTL in
	 * seconds. Delegates TTL validation to {@see InMemoryCacheStore::putRaw()}.
	 *
	 * @param string               $key        Cache key.
	 * @param array<string, mixed> $payload    Normalized search result.
	 * @param int                  $ttlSeconds TTL in seconds; must be > 0.
	 */
	public function put( string $key, array $payload, int $ttlSeconds ): void {
		$this->store->putRaw( $key, $payload, $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();
	}
}
