<?php
/**
 * REST namespace value class.
 *
 * Encapsulates the §2.1 ("API 基本情報 / Namespace") namespace constants
 * documented in `specifications/rest-api-spec.md` so every other slice
 * of the REST presentation layer references the canonical
 * `affilun` / `v1` / `affilun/v1` triple through one type rather than
 * scattering string literals across controllers, route registrars,
 * and tests.
 *
 * The class exposes both the raw constants (so PHP-level
 * `register_rest_route()` callers can still pick them apart) and a
 * pair of static helpers:
 *
 *   - {@see self::full()} returns the joined `affilun/v1` string suitable
 *     for the `$namespace` argument of WordPress's
 *     `register_rest_route()`.
 *
 *   - {@see self::route()} concatenates the namespace with a
 *     leading-slash-prefixed path so route definitions can be
 *     written as `RestNamespace::route('/products/search')` instead
 *     of hand-assembling string fragments. The leading slash is
 *     enforced so a typo (`'products/search'`) fails loudly instead
 *     of producing the run-together string `'affilun/v1products/search'`
 *     that WordPress would happily register and never match.
 *
 * This class does not perform any WordPress API calls and is safe to
 * load in pure-unit mode.
 *
 * @package Affilun\Presentation\Rest
 */

declare(strict_types=1);

namespace Affilun\Presentation\Rest;

use InvalidArgumentException;

/**
 * Holds the §2.1 REST namespace constants and small joining helpers.
 *
 * See the file-level docblock for the mapping to
 * `rest-api-spec.md` §2.1 and the rationale for enforcing a
 * leading-slash on the path argument of {@see self::route()}.
 */
final class RestNamespace {

	/**
	 * §2.1 base namespace token (the prefix portion of `affilun/v1`).
	 */
	public const NAMESPACE = 'affilun';

	/**
	 * §2.1 namespace version segment.
	 */
	public const VERSION = 'v1';

	/**
	 * §2.1 fully-joined namespace string.
	 *
	 * Provided as a constant in addition to {@see self::full()} so
	 * call sites that need a compile-time literal (for example
	 * attribute arguments) can reference it directly.
	 */
	public const FULL = 'affilun/v1';

	/**
	 * Return the §2.1 fully-joined namespace string.
	 *
	 * Mirrors {@see self::FULL} but in callable form so the helper
	 * can be passed wherever a `callable(): string` is expected.
	 */
	public static function full(): string {
		return self::FULL;
	}

	/**
	 * Join the §2.1 namespace with a leading-slash-prefixed path.
	 *
	 * The leading slash is enforced so a typo (for example
	 * `'products/search'` instead of `'/products/search'`) fails
	 * loudly here rather than silently producing the run-together
	 * string `'affilun/v1products/search'` that WordPress would happily
	 * register and never match.
	 *
	 * @param string $path Route path beginning with `/`.
	 *
	 * @throws InvalidArgumentException When `$path` is empty or does
	 *                                  not start with `/`.
	 */
	public static function route( string $path ): string {
		if ( $path === '' ) {
			$message = 'RestNamespace::route() path must be a non-empty string starting with "/".';
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}

		if ( $path[0] !== '/' ) {
			$message = sprintf(
				'RestNamespace::route() path must start with "/"; got "%s".',
				$path
			);
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}

		return self::FULL . $path;
	}
}
