<?php

/**
 * Custom Field Factory
 *
 * @package AmbassadorHub\CustomFields
 */

namespace AmbassadorHub\Fields;

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

use AmbassadorHub\Fields\Fields\AbstractField;

use function add_action;
use InvalidArgumentException;

/**
 * Factory for creating field instances by type.
 */
final class FieldFactory {
	/**
	 * Cached field instances by type.
	 *
	 * @var array<string,AbstractField>
	 */
	private static array $instances = array();
	/**
	 * Map of field type to class name.
	 *
	 * @var array<string,class-string>
	 */
	private static array $field_classes = array();

	/**
	 * Map of field type to label.
	 *
	 * @var array<string,string>
	 */
	private static array $labels = array();

	/**
	 * Get a field instance by type.
	 *
	 * @param string $type Field type (text, number, checkbox, radio, dropdown, address).
	 *
	 * @return AbstractField Field instance.
	 *
	 * @throws InvalidArgumentException If field type is not supported.
	 */
	public static function create( string $type ): AbstractField {
		if ( isset( self::$instances[ $type ] ) ) {
			return self::$instances[ $type ];
		}

		if ( ! isset( self::$field_classes[ $type ] ) ) {
			throw new InvalidArgumentException( sprintf( 'Invalid field type: %s', esc_html( $type ) ) );
		}

		$class    = self::$field_classes[ $type ];
		$instance = new $class();
		self::register_instance( $type, $instance );
		return $instance;
	}

	/**
	 * Register a field instance (used by field constructors).
	 *
	 * @param string      $type Field type identifier.
	 * @param AbstractField $instance Field instance.
	 *
	 * @return void
	 */
	public static function register_instance( string $type, AbstractField $instance ): void {
		self::$instances[ $type ] = $instance;
	}

	/**
	 * Get all registered field instances.
	 *
	 * @return array<string,AbstractField>
	 */
	public static function get_instances(): array {
		return self::$instances;
	}

	/**
	 * Register a custom field type.
	 *
	 * Allows plugins to register additional field types.
	 *
	 * @param string $type Field type identifier.
	 * @param class-string<AbstractField> $class_name Fully qualified class name extending AbstractField.
	 * @param string $label Human-readable label for the field type.
	 *
	 * @return void
	 *
	 * @throws InvalidArgumentException If class doesn't extend AbstractField.
	 */
	public static function register( string $type, string $class_name, string $label ): void {
		if ( ! class_exists( $class_name ) ) {
			throw new InvalidArgumentException( sprintf( 'Class not found: %s', esc_html( $class_name ) ) );
		}

		// Validate that class extends AbstractField
		if ( ! is_subclass_of( $class_name, AbstractField::class ) ) {
			throw new InvalidArgumentException(
				sprintf( 'Class %s must extend %s', esc_html( $class_name ), AbstractField::class )
			);
		}

		self::$field_classes[ $type ] = $class_name;
		self::$labels[ $type ]        = $label;

		// Lazy settings rendering hook. Instance is created when needed.
		add_action(
			"ambh_render_field_type_settings_{$type}",
			static function () use ( $type ): void {
				self::create( $type )->render_field_settings();
			}
		);
	}

	/**
	 * Get all registered field types.
	 *
	 * @return array<string,class-string> Field types keyed by type ID.
	 */
	public static function get_types(): array {
		return self::$field_classes;
	}

	/**
	 * Filter field type labels for consumers.
	 *
	 * @param array<string,string> $types Existing types.
	 *
	 * @return array<string,string>
	 */
	public static function filter_type_labels( array $types ): array {
		return array_merge( $types, self::$labels );
	}
}
