<?php
/**
 * Option-store backed settings repository.
 *
 * Production {@see SettingsRepository} implementation that
 * persists the §13 Settings aggregate as a single serialized
 * blob under the `clb_settings` option. The blob shape is
 * canonicalized by {@see PluginSettings::toArray()} /
 * {@see PluginSettings::fromArray()} so a future change to the
 * settings schema is a one-method change on
 * {@see PluginSettings} rather than a cross-cutting refactor
 * of every persistence call site.
 *
 * Recovery behavior:
 *
 *   - When the option is unset, {@see self::load()} returns
 *     {@see PluginSettings::defaults()}.
 *
 *   - When the option carries a value that is not an array, or
 *     when {@see PluginSettings::fromArray()} rejects the
 *     payload as malformed, {@see self::load()} ALSO returns
 *     {@see PluginSettings::defaults()}. This is the Phase-8
 *     minimum: silent recovery so the admin page remains
 *     functional even if the option was edited out-of-band or
 *     written by an older plugin version with an incompatible
 *     schema. Observability of the recovery path (a log line,
 *     an admin notice) is a Phase-11 concern; this slice
 *     deliberately keeps {@see self::load()} side-effect-free
 *     so it can be exercised in pure-unit tests without a
 *     logger fixture.
 *
 * @package CommerceLinkBuilder\Application\Settings
 */

declare(strict_types=1);

namespace CommerceLinkBuilder\Application\Settings;

use CommerceLinkBuilder\Infrastructure\Persistence\OptionStore;
use InvalidArgumentException;

/**
 * Persists {@see PluginSettings} as a single option-store blob.
 *
 * See the file-level docblock for the recovery rule that
 * gates {@see self::load()} when the stored payload is
 * malformed.
 */
final class OptionStoreSettingsRepository implements SettingsRepository {

	/**
	 * Canonical option key under which the §13 settings blob
	 * is persisted.
	 *
	 * Named here as the single source of truth so a future
	 * rename is a one-file change.
	 */
	public const OPTION_KEY = 'clb_settings';

	/**
	 * Underlying key/value store used to read and write the
	 * option.
	 */
	private OptionStore $optionStore;

	/**
	 * Construct the repository.
	 *
	 * @param OptionStore $optionStore Backing store for the option.
	 */
	public function __construct( OptionStore $optionStore ) {
		$this->optionStore = $optionStore;
	}

	/**
	 * Load the current settings snapshot.
	 *
	 * Returns {@see PluginSettings::defaults()} when the option
	 * is unset, when the stored value is not an array, or when
	 * {@see PluginSettings::fromArray()} rejects it as
	 * malformed. See the file-level docblock for the rationale
	 * for treating malformed payloads as a silent recovery
	 * branch in Phase 8.
	 */
	public function load(): PluginSettings {
		$stored = $this->optionStore->get( self::OPTION_KEY, null );

		if ( ! is_array( $stored ) ) {
			return PluginSettings::defaults();
		}

		try {
			return PluginSettings::fromArray( $stored );
		} catch ( InvalidArgumentException $unused ) {
			unset( $unused );
			return PluginSettings::defaults();
		}
	}

	/**
	 * Persist the supplied settings snapshot.
	 *
	 * The blob is serialized via {@see PluginSettings::toArray()}
	 * and written via {@see OptionStore::update()}. Errors
	 * from the underlying store propagate.
	 *
	 * @param PluginSettings $settings Snapshot to persist.
	 */
	public function save( PluginSettings $settings ): void {
		$this->optionStore->update( self::OPTION_KEY, $settings->toArray() );
	}

	/**
	 * Delete the stored settings blob.
	 *
	 * After a successful call, the next {@see self::load()}
	 * returns {@see PluginSettings::defaults()}.
	 */
	public function reset(): void {
		$this->optionStore->delete( self::OPTION_KEY );
	}
}
