<?php
/**
 * Settings form-token helper.
 *
 * Phase-8 slice 8c-2 static helper that encapsulates the
 * WordPress nonce action / field / form-action conventions for
 * the §13 Settings admin page documented in
 * `specifications/admin-ui-spec.md` §13.10. Centralizing the
 * literals keeps the form renderer
 * ({@see SettingsPageRenderer}), the form parser
 * ({@see SettingsFormParser}), and the POST handler
 * ({@see SaveSettingsHandler}) in agreement on the four pieces
 * of the §13.10 save round-trip:
 *
 *   - `NONCE_ACTION` — the literal passed to
 *     `wp_create_nonce()` / `wp_verify_nonce()`.
 *   - `NONCE_FIELD`  — the `<input type="hidden" name="...">`
 *     name the renderer emits via
 *     `wp_nonce_field($action, $name)` and the handler reads
 *     from `$_POST`.
 *   - `FORM_FIELD_PREFIX` — the top-level `$_POST` key under
 *     which every settings field is namespaced
 *     (`clb_settings[rendering][default_template]` etc.). The
 *     parser reads `$_POST[FORM_FIELD_PREFIX]` as the root of
 *     the form payload.
 *   - `FORM_ACTION` — the `admin-post.php?action=...` value
 *     that drives WordPress's `admin_post_{action}` action
 *     dispatch. The renderer emits it as the `<input
 *     name="action">` value; the {@see \CommerceLinkBuilder\Presentation\Admin\AdminServiceProvider}
 *     subscribes the corresponding `admin_post_clb_save_settings`
 *     hook.
 *
 * The {@see self::validateNonce()} helper exists so the POST
 * handler ({@see SaveSettingsHandler}) can perform nonce
 * verification through an injected closure rather than calling
 * `wp_verify_nonce()` directly. That keeps the handler fully
 * testable in pure-unit mode where WordPress is not loaded;
 * the production wiring forwards to `wp_verify_nonce()`
 * verbatim. Per §6.4 the nonce value itself is never logged.
 *
 * @package CommerceLinkBuilder\Presentation\Admin\Page
 */

declare(strict_types=1);

namespace CommerceLinkBuilder\Presentation\Admin\Page;

/**
 * Static helper carrying the §13.10 save-form literals (nonce
 * action / nonce field / form-field prefix / admin-post action).
 *
 * See the file-level docblock for how the §13 Settings admin
 * page's renderer / parser / POST handler share these literals
 * to round-trip a save request through `admin-post.php`.
 */
final class SettingsFormToken {

	/**
	 * Action literal passed to `wp_create_nonce()` /
	 * `wp_verify_nonce()`.
	 */
	public const NONCE_ACTION = 'clb_settings_save';

	/**
	 * `<input type="hidden">` name the renderer emits via
	 * `wp_nonce_field()` and the handler reads from `$_POST`.
	 */
	public const NONCE_FIELD = '_clb_settings_nonce';

	/**
	 * Top-level `$_POST` key under which every settings field is
	 * namespaced.
	 */
	public const FORM_FIELD_PREFIX = 'clb_settings';

	/**
	 * `admin-post.php?action=...` value driving WordPress's
	 * `admin_post_{action}` dispatch.
	 */
	public const FORM_ACTION = 'clb_save_settings';

	/**
	 * Return the nonce action literal.
	 */
	public static function nonceAction(): string {
		return self::NONCE_ACTION;
	}

	/**
	 * Return the nonce-field name literal.
	 */
	public static function nonceField(): string {
		return self::NONCE_FIELD;
	}

	/**
	 * Return the form-field prefix literal.
	 */
	public static function formFieldPrefix(): string {
		return self::FORM_FIELD_PREFIX;
	}

	/**
	 * Return the `admin-post.php` action literal.
	 */
	public static function formAction(): string {
		return self::FORM_ACTION;
	}

	/**
	 * Validate a nonce value against {@see self::NONCE_ACTION}
	 * via the supplied verifier closure.
	 *
	 * The closure abstracts `wp_verify_nonce()` so tests can
	 * inject fake verifiers without touching globals; production
	 * wiring forwards the call verbatim.
	 *
	 * @param callable $verifier   Verifier closure. Signature:
	 *                             `function (string $action, string $value): bool`.
	 * @param string   $nonceValue Nonce value to verify.
	 *
	 * @phpstan-param callable(string, string): bool $verifier
	 */
	public static function validateNonce( callable $verifier, string $nonceValue ): bool {
		return (bool) $verifier( self::NONCE_ACTION, $nonceValue );
	}
}
