<?php
/**
 * Option storage abstraction.
 *
 * Commerce Link Builder persists a small set of plugin-scoped values
 * (schema version, feature flags, provider health caches) in the
 * WordPress `options` table. Those call sites do not need the full
 * surface of the WordPress options API — they only need to read, write,
 * and delete values under known keys, with an explicit default.
 *
 * Wrapping that narrow surface in an interface lets the persistence
 * layer stay unit-testable without a running WordPress install: tests
 * inject an {@see InMemoryOptionStore} fake, while production code uses
 * {@see WpOptionStore} which delegates to `get_option()` /
 * `update_option()` / `delete_option()`.
 *
 * Implementations MUST:
 *
 *   - Return the supplied `$default` verbatim when the key is unset.
 *     A null default is a legitimate sentinel and must be preserved.
 *
 *   - Treat {@see self::update()} as an upsert. Callers should not have
 *     to distinguish "first write" from "overwrite".
 *
 *   - Return a boolean from {@see self::delete()} indicating whether a
 *     value existed prior to deletion, matching WordPress semantics.
 *
 * Implementations MAY perform autoload tuning, serialization, or value
 * normalization, but MUST NOT throw on missing keys — that is what the
 * default parameter is for.
 *
 * @package CommerceLinkBuilder\Infrastructure\Persistence
 */

declare(strict_types=1);

namespace CommerceLinkBuilder\Infrastructure\Persistence;

/**
 * Narrow key/value contract over the WordPress options table.
 *
 * See the file-level docblock for the full semantics.
 */
interface OptionStore {

	/**
	 * Read the value stored under the given key.
	 *
	 * When the key is unset, the supplied `$fallback` is returned verbatim.
	 * Implementations MUST preserve the type of stored scalars and arrays
	 * when WordPress serialization would otherwise normalize them.
	 *
	 * The parameter is named `$fallback` rather than `$default` to avoid
	 * the PHP reserved keyword warning raised by PHPCS's
	 * Universal.NamingConventions.NoReservedKeywordParameterNames sniff;
	 * the semantics are identical to WordPress's `get_option( $key, $default )`.
	 *
	 * @param string $key      Option key, including any plugin prefix.
	 * @param mixed  $fallback Value returned when the key is unset.
	 *
	 * @return mixed The stored value, or `$fallback` when unset.
	 */
	public function get( string $key, $fallback = null );

	/**
	 * Write the value under the given key (upsert).
	 *
	 * Callers do not need to distinguish "first write" from "overwrite";
	 * implementations MUST perform whichever operation is appropriate.
	 * The return value reports whether the stored value changed as a
	 * result of the call, matching WordPress's `update_option()`
	 * semantics (identical writes return false).
	 *
	 * @param string $key   Option key, including any plugin prefix.
	 * @param mixed  $value Value to store. Any type accepted by WordPress.
	 *
	 * @return bool True when the stored value changed, false otherwise.
	 */
	public function update( string $key, $value ): bool;

	/**
	 * Delete the value stored under the given key.
	 *
	 * @param string $key Option key, including any plugin prefix.
	 *
	 * @return bool True when a value existed and was removed, false when
	 *              no value was stored under the key.
	 */
	public function delete( string $key ): bool;

	/**
	 * Report whether a value is stored under the given key.
	 *
	 * Intended for call sites that need to distinguish "unset" from
	 * "set to a falsy value"; most callers should prefer {@see self::get()}
	 * with an explicit default instead.
	 *
	 * @param string $key Option key, including any plugin prefix.
	 */
	public function has( string $key ): bool;
}
