<?php

namespace SugarCalendarBookings\Antispam\Captcha;

use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;

/**
 * Catalog of available captcha providers.
 *
 * Maps the stored `antispam.method` key directly to a concrete verifier class.
 * Adding a provider = new class + one entry in PROVIDERS.
 *
 * @since 1.5.0
 */
final class CaptchaVerifierRegistry {

	/**
	 * Method-key → verifier class map.
	 *
	 * @since 1.5.0
	 *
	 * @var array<string, class-string<AbstractCaptchaVerifier>>
	 */
	private const PROVIDERS = [
		'checkbox_recaptcha_v2'  => RecaptchaV2::class,
		'invisible_recaptcha_v2' => RecaptchaV2::class,
		'recaptcha_v3'           => RecaptchaV3::class,
		'turnstile'              => Turnstile::class,
	];

	/**
	 * Settings classes registered for the Anti-Spam tab.
	 *
	 * Flat class-list; the category key for each instance is derived from
	 * its own `get_key()` when the cache is built. One source of truth — no
	 * docblock-only "must match CATEGORY" coupling.
	 *
	 * @since 1.5.0
	 *
	 * @var array<int, class-string<Settings\AbstractCaptchaSettings>>
	 */
	private const SETTINGS = [
		Settings\RecaptchaSettings::class,
		Settings\TurnstileSettings::class,
	];

	/**
	 * Lazy-built settings instances, keyed by category.
	 *
	 * @since 1.5.0
	 *
	 * @var array<string, Settings\CaptchaSettingsInterface>|null
	 */
	private static ?array $settings_instances = null;

	/**
	 * Resolve the verifier class for the currently configured method.
	 *
	 * Returns null when antispam is disabled or the stored method is unknown.
	 *
	 * @since 1.5.0
	 *
	 * @return class-string<AbstractCaptchaVerifier>|null
	 */
	public static function resolve_active_class(): ?string {

		$method = (string) Plugin::get( Options::class )->get( 'antispam.method' );

		return self::PROVIDERS[ $method ] ?? null;
	}

	/**
	 * All known providers, keyed by stored method key.
	 *
	 * @since 1.5.0
	 *
	 * @return array<string, class-string<AbstractCaptchaVerifier>>
	 */
	public static function all(): array {

		return self::PROVIDERS;
	}

	/**
	 * Resolve a representative verifier class for a UI category.
	 *
	 * Walks the registered providers and returns the first one whose `CATEGORY`
	 * constant matches. Providers sharing a category share option keys, so any
	 * one of them answers questions like `has_complete_keys()` for the whole
	 * category.
	 *
	 * @since 1.5.0
	 *
	 * @param string $category UI category key (e.g. 'recaptcha', 'turnstile').
	 *
	 * @return class-string<AbstractCaptchaVerifier>|null
	 */
	public static function class_for_category( string $category ): ?string {

		if ( $category === '' ) {
			return null;
		}

		foreach ( self::PROVIDERS as $class ) {
			if ( $category === $class::CATEGORY ) {
				return $class;
			}
		}

		return null;
	}

	/**
	 * All registered settings classes, instantiated and keyed by category.
	 *
	 * @since 1.5.0
	 *
	 * @return array<string, Settings\CaptchaSettingsInterface>
	 */
	public static function all_settings(): array {

		if ( self::$settings_instances === null ) {
			self::$settings_instances = [];

			foreach ( self::SETTINGS as $class ) {
				$instance = new $class();
				self::$settings_instances[ $instance->get_key() ] = $instance;
			}
		}

		return self::$settings_instances;
	}

	/**
	 * Reset the lazy settings-instance cache.
	 *
	 * Test-only escape hatch — production code never needs this.
	 *
	 * @since 1.5.0
	 *
	 * @internal
	 *
	 * @return void
	 */
	public static function reset_settings_cache(): void {

		self::$settings_instances = null;
	}

	/**
	 * Resolve the settings class that owns the given stored method-key.
	 *
	 * @since 1.5.0
	 *
	 * @param string $method Stored flat method-key.
	 *
	 * @return Settings\CaptchaSettingsInterface|null
	 */
	public static function resolve_settings_for_method( string $method ): ?Settings\CaptchaSettingsInterface {

		foreach ( self::all_settings() as $settings ) {
			if ( $settings->owns_method( $method ) ) {
				return $settings;
			}
		}

		return null;
	}
}
