<?php
/**
 * Neutral free-namespace bridge to the premium server-side layer.
 *
 * @package Carticy\Conversions\Rest
 */

declare(strict_types=1);

namespace Carticy\Conversions\Rest;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Lets the shipped REST controllers expose premium server-side settings and
 * trigger the dispatch sweep WITHOUT naming any premium class.
 *
 * The WordPress.org release gate fails the build if any shipped first-party file
 * references the premium ServerSide / Premium namespaces (only `src/Init.php` is
 * exempt). So the premium accessors are authored as closures inside `Init` - the
 * one place those references are legal - and handed here as opaque callables.
 * This object holds plain data + closures and contains zero premium references;
 * in the free build it is constructed via {@see self::unavailable()} and every
 * accessor short-circuits.
 */
final class PremiumServerBridge {

	/**
	 * Whether the premium server-side layer is available and licensed.
	 *
	 * @var bool
	 */
	private bool $available;

	/**
	 * Reader closure returning the server settings as a redaction-ready array.
	 *
	 * @var \Closure|null
	 */
	private ?\Closure $reader;

	/**
	 * Writer closure persisting a sanitized, secret-preserving settings patch.
	 *
	 * @var \Closure|null
	 */
	private ?\Closure $writer;

	/**
	 * Kicker closure scheduling an immediate dispatch sweep.
	 *
	 * @var \Closure|null
	 */
	private ?\Closure $kicker;

	/**
	 * Constructor.
	 *
	 * @param bool          $available Whether the premium layer is available.
	 * @param \Closure|null $reader Reader closure (server settings - array).
	 * @param \Closure|null $writer Writer closure (settings patch - void).
	 * @param \Closure|null $kicker Kicker closure (schedule immediate sweep).
	 */
	public function __construct( bool $available, ?\Closure $reader = null, ?\Closure $writer = null, ?\Closure $kicker = null ) {
		$this->available = $available;
		$this->reader    = $reader;
		$this->writer    = $writer;
		$this->kicker    = $kicker;
	}

	/**
	 * Build the free-build bridge: the premium layer is absent.
	 *
	 * @return self
	 */
	public static function unavailable(): self {
		return new self( false );
	}

	/**
	 * Whether the premium server-side layer is available and licensed.
	 *
	 * @return bool
	 */
	public function available(): bool {
		return $this->available && null !== $this->reader;
	}

	/**
	 * Read the server settings as a redaction-ready array.
	 *
	 * The returned shape carries secret-PRESENCE flags (e.g. `api_secret_set`),
	 * never the secret value itself; the controller turns those into the masked
	 * read contract.
	 *
	 * @return array<string, mixed>
	 */
	public function read(): array {
		if ( ! $this->available() ) {
			return array();
		}

		$reader = $this->reader;
		$value  = $reader();

		return is_array( $value ) ? $value : array();
	}

	/**
	 * Persist a sanitized, secret-preserving settings patch.
	 *
	 * @param array<string, mixed> $patch Settings patch (write-only secrets preserved when blank).
	 */
	public function save( array $patch ): void {
		if ( ! $this->available() || null === $this->writer ) {
			return;
		}

		$writer = $this->writer;
		$writer( $patch );
	}

	/**
	 * Schedule an immediate dispatch sweep so newly-saved credentials flush the
	 * `blocked_missing_config` rows promptly.
	 */
	public function kick_queue(): void {
		if ( ! $this->available() || null === $this->kicker ) {
			return;
		}

		$kicker = $this->kicker;
		$kicker();
	}
}
