<?php
/**
 * Custom Fields Module
 *
 * Standalone module for course-specific booking fields and variant extra fields.
 *
 * Features:
 * - Teil A: Override global fields per course + add custom fields
 * - Teil B: Link extra fields to price variants (quantity or text)
 *
 * @package KursBooking
 * @since   1.3.0
 */

declare(strict_types=1);

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Kurs_Booking_Custom_Fields
 *
 * Modular handler for course-specific booking fields and variant extra fields.
 */
class Kurs_Booking_Custom_Fields {

	/**
	 * Meta key for disabled global fields per course.
	 */
	public const META_DISABLED_GLOBAL = '_kurs_booking_fields_override';

	/**
	 * Meta key for course-specific custom fields.
	 */
	public const META_KURS_FIELDS = '_kurs_booking_fields';

	/**
	 * Meta key for stored extra field data in booking.
	 */
	public const META_BOOKING_EXTRA = '_buchung_extra_fields';

	/**
	 * Allowed field types for course-specific fields.
	 */
	public const ALLOWED_TYPES = array( 'text', 'email', 'tel', 'date', 'textarea', 'select', 'radio', 'checkbox', 'number' );

	/**
	 * Extra field types for variants.
	 */
	public const EXTRA_FIELD_TYPES = array( 'quantity', 'text' );

	// ═══════════════════════════════════════════════════════════════════════.
	// INITIALIZATION.
	// ═══════════════════════════════════════════════════════════════════════.

	/**
	 * Initialize the custom fields module.
	 */
	public static function init(): void {
		// Register metabox for course editor.
		add_action( 'add_meta_boxes', array( __CLASS__, 'register_metabox' ) );
		add_action( 'save_post_kurs', array( __CLASS__, 'save_metabox' ), 15, 2 );

		// Enqueue scripts.
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_admin_scripts' ) );
		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_frontend_scripts' ) );

		// Filter for variant saving (hook into class-kurs.php).
		add_filter( 'kurs_booking_sanitize_variant', array( __CLASS__, 'sanitize_variant_extra_field' ), 10, 2 );
	}

	/**
	 * Enqueue admin scripts for custom fields.
	 *
	 * @param string $hook_suffix Current admin page.
	 */
	public static function enqueue_admin_scripts( string $hook_suffix ): void {
		$screen = get_current_screen();

		if ( ! $screen || 'kurs' !== $screen->post_type ) {
			return;
		}

		$plugin_url = KURS_BOOKING_URL;

		// Custom fields CSS (always in admin).
		if ( file_exists( KURS_BOOKING_PATH . 'assets/css/custom-fields.css' ) ) {
			wp_enqueue_style(
				'kurs-booking-custom-fields',
				$plugin_url . 'assets/css/custom-fields.css',
				array(),
				KURS_BOOKING_VERSION
			);
		}

		// Custom fields JS (only on edit screen).
		if ( in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true ) ) {
			if ( file_exists( KURS_BOOKING_PATH . 'assets/js/custom-fields.js' ) ) {
				wp_enqueue_script(
					'kurs-booking-custom-fields',
					$plugin_url . 'assets/js/custom-fields.js',
					array( 'jquery' ),
					KURS_BOOKING_VERSION,
					true
				);

				wp_localize_script(
					'kurs-booking-custom-fields',
					'kursBookingCustomFields',
					array(
						'allowedTypes'   => self::ALLOWED_TYPES,
						'extraFieldTypes' => self::EXTRA_FIELD_TYPES,
						'i18n'           => array(
							'addField'       => __( 'Feld hinzufuegen', 'kurs-booking' ),
							'removeField'    => __( 'Entfernen', 'kurs-booking' ),
							'fieldLabel'     => __( 'Bezeichnung', 'kurs-booking' ),
							'fieldType'      => __( 'Feldtyp', 'kurs-booking' ),
							'mandatory'      => __( 'Pflichtfeld', 'kurs-booking' ),
							'quantity'       => __( 'Mengenfeld', 'kurs-booking' ),
							'textField'      => __( 'Info-Feld', 'kurs-booking' ),
							'min'            => __( 'Min', 'kurs-booking' ),
							'max'            => __( 'Max', 'kurs-booking' ),
							'multiplyPrice'  => __( 'Preis multiplizieren', 'kurs-booking' ),
						),
					)
				);
			}
		}
	}

	/**
	 * Enqueue frontend scripts for custom fields.
	 */
	public static function enqueue_frontend_scripts(): void {
		// Only enqueue if custom-fields.js exists.
		if ( ! file_exists( KURS_BOOKING_PATH . 'assets/js/custom-fields.js' ) ) {
			return;
		}

		// Will be conditionally loaded by booking form if needed.
	}

	// ═══════════════════════════════════════════════════════════════════════.
	// TEIL A: KURS-SPEZIFISCHE FELDER.
	// ═══════════════════════════════════════════════════════════════════════.

	/**
	 * Register metabox for custom fields in course editor.
	 */
	public static function register_metabox(): void {
		add_meta_box(
			'kurs_booking_custom_fields',
			__( 'Buchungsfelder', 'kurs-booking' ),
			array( __CLASS__, 'render_metabox' ),
			'kurs',
			'normal',
			'default'
		);
	}

	/**
	 * Render the custom fields metabox.
	 *
	 * @param WP_Post $post Current post object.
	 */
	public static function render_metabox( WP_Post $post ): void {
		wp_nonce_field( 'kurs_booking_custom_fields_nonce', 'kurs_custom_fields_nonce' );

		$global_fields      = self::get_global_fields_with_ids();
		$disabled_global    = self::get_disabled_global_fields( $post->ID );
		$kurs_fields        = self::get_kurs_specific_fields( $post->ID );

		// Include template if exists, otherwise inline.
		$template_path = KURS_BOOKING_PATH . 'templates/admin/custom-fields-metabox.php';
		if ( file_exists( $template_path ) ) {
			include $template_path;
		} else {
			self::render_metabox_inline( $global_fields, $disabled_global, $kurs_fields );
		}
	}

	/**
	 * Render metabox inline (fallback if template doesn't exist).
	 *
	 * @param array<int, array<string, mixed>> $global_fields   Global fields with IDs.
	 * @param array<int>                       $disabled_global Disabled global field IDs.
	 * @param array<int, array<string, mixed>> $kurs_fields     Course-specific fields.
	 */
	private static function render_metabox_inline( array $global_fields, array $disabled_global, array $kurs_fields ): void {
		?>
		<div class="kb-custom-fields-metabox">
			<!-- Global Fields Override -->
			<div class="kb-global-fields-section">
				<h4><?php esc_html_e( 'Globale Felder', 'kurs-booking' ); ?></h4>
				<p class="description">
					<?php esc_html_e( 'Deaktivieren Sie globale Felder, die fuer diesen Kurs nicht relevant sind.', 'kurs-booking' ); ?>
				</p>

				<?php if ( empty( $global_fields ) ) : ?>
					<p class="kb-no-fields">
						<?php esc_html_e( 'Keine globalen Buchungsfelder definiert.', 'kurs-booking' ); ?>
						<a href="<?php echo esc_url( admin_url( 'admin.php?page=kurs-booking-settings&tab=fields' ) ); ?>">
							<?php esc_html_e( 'Felder konfigurieren', 'kurs-booking' ); ?>
						</a>
					</p>
				<?php else : ?>
					<div class="kb-global-fields-list">
						<?php foreach ( $global_fields as $index => $field ) : ?>
							<label class="kb-global-field-item">
								<input type="checkbox"
									   name="kurs_global_fields_enabled[]"
									   value="<?php echo esc_attr( (string) $index ); ?>"
									   <?php checked( ! in_array( $index, $disabled_global, true ) ); ?>
								/>
								<span class="kb-field-label">
									<?php echo esc_html( $field['label'] ); ?>
								</span>
								<span class="kb-field-type">
									(<?php echo esc_html( $field['type'] ); ?>)
								</span>
								<?php if ( ! empty( $field['mandatory'] ) ) : ?>
									<span class="kb-field-mandatory">*</span>
								<?php endif; ?>
							</label>
						<?php endforeach; ?>
					</div>
				<?php endif; ?>
			</div>

			<hr />

			<!-- Course-Specific Fields -->
			<div class="kb-kurs-fields-section">
				<h4><?php esc_html_e( 'Kurs-spezifische Felder', 'kurs-booking' ); ?></h4>
				<p class="description">
					<?php esc_html_e( 'Zusaetzliche Felder nur fuer diesen Kurs.', 'kurs-booking' ); ?>
				</p>

				<div class="kb-kurs-fields-list" id="kb-kurs-fields-list">
					<?php foreach ( $kurs_fields as $index => $field ) : ?>
						<?php self::render_kurs_field_row( $field, $index ); ?>
					<?php endforeach; ?>
				</div>

				<button type="button" class="button" id="kb-add-kurs-field">
					<?php esc_html_e( '+ Feld hinzufuegen', 'kurs-booking' ); ?>
				</button>
			</div>
		</div>

		<!-- Template for new field row (hidden) -->
		<script type="text/html" id="kb-kurs-field-template">
			<?php self::render_kurs_field_row( array(), '{{INDEX}}' ); ?>
		</script>
		<?php
	}

	/**
	 * Render a single course-specific field row.
	 *
	 * Public for use in admin template.
	 *
	 * @param array<string, mixed> $field Field data.
	 * @param int|string           $index Row index.
	 */
	public static function render_kurs_field_row( array $field, int|string $index ): void {
		$label       = $field['label'] ?? '';
		$type        = $field['type'] ?? 'text';
		$placeholder = $field['placeholder'] ?? '';
		$mandatory   = ! empty( $field['mandatory'] );
		$options     = $field['options'] ?? array();
		?>
		<div class="kb-kurs-field-row" data-index="<?php echo esc_attr( (string) $index ); ?>">
			<div class="kb-field-row-inner">
				<input type="text"
					   name="kurs_fields[<?php echo esc_attr( (string) $index ); ?>][label]"
					   value="<?php echo esc_attr( $label ); ?>"
					   placeholder="<?php esc_attr_e( 'Bezeichnung', 'kurs-booking' ); ?>"
					   class="kb-field-label-input"
				/>

				<select name="kurs_fields[<?php echo esc_attr( (string) $index ); ?>][type]" class="kb-field-type-select">
					<?php foreach ( self::ALLOWED_TYPES as $allowed_type ) : ?>
						<option value="<?php echo esc_attr( $allowed_type ); ?>" <?php selected( $type, $allowed_type ); ?>>
							<?php echo esc_html( ucfirst( $allowed_type ) ); ?>
						</option>
					<?php endforeach; ?>
				</select>

				<label class="kb-field-mandatory-label">
					<input type="checkbox"
						   name="kurs_fields[<?php echo esc_attr( (string) $index ); ?>][mandatory]"
						   value="1"
						   <?php checked( $mandatory ); ?>
					/>
					<?php esc_html_e( 'Pflicht', 'kurs-booking' ); ?>
				</label>

				<button type="button" class="button kb-remove-kurs-field">
					&times;
				</button>
			</div>

			<div class="kb-field-options-row" style="<?php echo in_array( $type, array( 'select', 'radio' ), true ) ? '' : 'display:none;'; ?>">
				<input type="text"
					   name="kurs_fields[<?php echo esc_attr( (string) $index ); ?>][options]"
					   value="<?php echo esc_attr( implode( ', ', $options ) ); ?>"
					   placeholder="<?php esc_attr_e( 'Optionen (kommagetrennt)', 'kurs-booking' ); ?>"
					   class="kb-field-options-input widefat"
				/>
			</div>
		</div>
		<?php
	}

	/**
	 * Save metabox data.
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 */
	public static function save_metabox( int $post_id, WP_Post $post ): void {
		// Verify nonce.
		if ( ! isset( $_POST['kurs_custom_fields_nonce'] ) ||
			 ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['kurs_custom_fields_nonce'] ) ), 'kurs_booking_custom_fields_nonce' ) ) {
			return;
		}

		// Check autosave.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		// Check permissions.
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		// Save disabled global fields.
		$global_fields = self::get_global_fields_with_ids();
		$enabled_ids   = isset( $_POST['kurs_global_fields_enabled'] ) && is_array( $_POST['kurs_global_fields_enabled'] )
			? array_map( 'absint', $_POST['kurs_global_fields_enabled'] )
			: array();

		// Calculate disabled IDs (all global field IDs minus enabled ones).
		$all_ids      = array_keys( $global_fields );
		$disabled_ids = array_values( array_diff( $all_ids, $enabled_ids ) );

		update_post_meta( $post_id, self::META_DISABLED_GLOBAL, wp_json_encode( $disabled_ids ) );

		// Save course-specific fields.
		$kurs_fields = array();
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_kurs_field() below.
		if ( isset( $_POST['kurs_fields'] ) && is_array( $_POST['kurs_fields'] ) ) {
			foreach ( wp_unslash( $_POST['kurs_fields'] ) as $field_data ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				if ( ! is_array( $field_data ) || empty( $field_data['label'] ) ) {
					continue;
				}

				$sanitized = self::sanitize_kurs_field( $field_data );
				if ( null !== $sanitized ) {
					$kurs_fields[] = $sanitized;
				}
			}
		}

		update_post_meta( $post_id, self::META_KURS_FIELDS, wp_json_encode( $kurs_fields ) );
	}

	/**
	 * Sanitize a course-specific field.
	 *
	 * @param array<string, mixed> $field_data Raw field data.
	 * @return array<string, mixed>|null Sanitized field or null if invalid.
	 */
	private static function sanitize_kurs_field( array $field_data ): ?array {
		$type = sanitize_key( $field_data['type'] ?? 'text' );
		if ( ! in_array( $type, self::ALLOWED_TYPES, true ) ) {
			$type = 'text';
		}

		$sanitized = array(
			'type'        => $type,
			'label'       => sanitize_text_field( $field_data['label'] ?? '' ),
			'placeholder' => sanitize_text_field( $field_data['placeholder'] ?? '' ),
			'mandatory'   => ! empty( $field_data['mandatory'] ),
		);

		// Handle options for select/radio.
		if ( in_array( $type, array( 'select', 'radio' ), true ) && ! empty( $field_data['options'] ) ) {
			$options_raw = is_array( $field_data['options'] )
				? $field_data['options']
				: array_map( 'trim', explode( ',', (string) $field_data['options'] ) );

			$sanitized['options'] = array_filter( array_map( 'sanitize_text_field', $options_raw ) );
		}

		return empty( $sanitized['label'] ) ? null : $sanitized;
	}

	/**
	 * Get global fields with numeric IDs.
	 *
	 * @return array<int, array<string, mixed>> Global fields indexed by position.
	 */
	public static function get_global_fields_with_ids(): array {
		if ( ! class_exists( 'Kurs_Booking_Settings' ) ) {
			return array();
		}

		return Kurs_Booking_Settings::get_custom_fields();
	}

	/**
	 * Get disabled global field IDs for a course.
	 *
	 * @param int $kurs_id Course ID.
	 * @return array<int> Array of disabled field indices.
	 */
	public static function get_disabled_global_fields( int $kurs_id ): array {
		$json = get_post_meta( $kurs_id, self::META_DISABLED_GLOBAL, true );
		if ( empty( $json ) ) {
			return array();
		}

		$disabled = json_decode( $json, true );
		return is_array( $disabled ) ? array_map( 'intval', $disabled ) : array();
	}

	/**
	 * Get course-specific custom fields.
	 *
	 * @param int $kurs_id Course ID.
	 * @return array<int, array<string, mixed>> Course-specific fields.
	 */
	public static function get_kurs_specific_fields( int $kurs_id ): array {
		$json = get_post_meta( $kurs_id, self::META_KURS_FIELDS, true );
		if ( empty( $json ) ) {
			return array();
		}

		$fields = json_decode( $json, true );
		return is_array( $fields ) ? $fields : array();
	}

	/**
	 * Get merged fields for a course (active global + course-specific).
	 *
	 * @param int $kurs_id Course ID.
	 * @return array<int, array<string, mixed>> Merged fields ready for rendering.
	 */
	public static function get_merged_fields( int $kurs_id ): array {
		$global_fields   = self::get_global_fields_with_ids();
		$disabled_global = self::get_disabled_global_fields( $kurs_id );
		$kurs_fields     = self::get_kurs_specific_fields( $kurs_id );

		$merged = array();

		// Add active global fields.
		foreach ( $global_fields as $index => $field ) {
			if ( ! in_array( $index, $disabled_global, true ) ) {
				$field['source'] = 'global';
				$field['id']     = 'global_' . $index;
				$merged[]        = $field;
			}
		}

		// Add course-specific fields.
		foreach ( $kurs_fields as $index => $field ) {
			$field['source'] = 'kurs';
			$field['id']     = 'kurs_' . $index;
			$merged[]        = $field;
		}

		return $merged;
	}

	// ═══════════════════════════════════════════════════════════════════════.
	// TEIL B: VARIANTEN-ZUSATZFELDER.
	// ═══════════════════════════════════════════════════════════════════════.

	/**
	 * Render variant extra field UI in admin.
	 *
	 * @param array<string, mixed> $variant Variant data.
	 * @param int                  $index   Variant index.
	 */
	public static function render_variant_extra_field_ui( array $variant, int $index ): void {
		$extra_field = $variant['extra_field'] ?? null;
		$has_extra   = ! empty( $extra_field );
		$type        = $extra_field['type'] ?? 'quantity';
		$label       = $extra_field['label'] ?? '';
		$required    = ! empty( $extra_field['required'] );
		$min         = $extra_field['min'] ?? 1;
		$max         = $extra_field['max'] ?? 10;
		$multiply    = ! empty( $extra_field['multiply_price'] );
		?>
		<div class="kb-variant-extra-field" data-variant-index="<?php echo esc_attr( (string) $index ); ?>">
			<label class="kb-has-extra-field">
				<input type="checkbox"
					   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][has_extra_field]"
					   value="1"
					   class="kb-extra-field-toggle"
					   <?php checked( $has_extra ); ?>
				/>
				<?php esc_html_e( 'Hat Zusatzfeld', 'kurs-booking' ); ?>
			</label>

			<div class="kb-extra-field-options" style="<?php echo $has_extra ? '' : 'display:none;'; ?>">
				<div class="kb-extra-field-row">
					<label class="kb-extra-type-option">
						<input type="radio"
							   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][type]"
							   value="quantity"
							   <?php checked( $type, 'quantity' ); ?>
						/>
						<?php esc_html_e( 'Mengenfeld', 'kurs-booking' ); ?>
					</label>
					<label class="kb-extra-type-option">
						<input type="radio"
							   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][type]"
							   value="text"
							   <?php checked( $type, 'text' ); ?>
						/>
						<?php esc_html_e( 'Info-Feld', 'kurs-booking' ); ?>
					</label>
				</div>

				<div class="kb-extra-field-row">
					<input type="text"
						   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][label]"
						   value="<?php echo esc_attr( $label ); ?>"
						   placeholder="<?php esc_attr_e( 'Bezeichnung', 'kurs-booking' ); ?>"
						   class="widefat"
					/>
				</div>

				<div class="kb-extra-field-row kb-quantity-options" style="<?php echo 'quantity' === $type ? '' : 'display:none;'; ?>">
					<label>
						<?php esc_html_e( 'Min:', 'kurs-booking' ); ?>
						<input type="number"
							   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][min]"
							   value="<?php echo esc_attr( (string) $min ); ?>"
							   min="0"
							   max="100"
							   style="width:60px;"
						/>
					</label>
					<label>
						<?php esc_html_e( 'Max:', 'kurs-booking' ); ?>
						<input type="number"
							   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][max]"
							   value="<?php echo esc_attr( (string) $max ); ?>"
							   min="1"
							   max="100"
							   style="width:60px;"
						/>
					</label>
					<label>
						<input type="checkbox"
							   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][multiply_price]"
							   value="1"
							   <?php checked( $multiply ); ?>
						/>
						<?php esc_html_e( 'Preis multiplizieren', 'kurs-booking' ); ?>
					</label>
				</div>

				<div class="kb-extra-field-row">
					<label>
						<input type="checkbox"
							   name="kurs_varianten[<?php echo esc_attr( (string) $index ); ?>][extra_field][required]"
							   value="1"
							   <?php checked( $required ); ?>
						/>
						<?php esc_html_e( 'Pflichtfeld', 'kurs-booking' ); ?>
					</label>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Sanitize variant extra field from POST data.
	 *
	 * Filter hook for kurs_booking_sanitize_variant.
	 *
	 * @param array<string, mixed> $variant Variant data being saved.
	 * @param array<string, mixed> $input   Raw input data.
	 * @return array<string, mixed> Variant with sanitized extra_field.
	 */
	public static function sanitize_variant_extra_field( array $variant, array $input ): array {
		// Check if variant has extra field enabled.
		if ( empty( $input['has_extra_field'] ) ) {
			unset( $variant['extra_field'] );
			return $variant;
		}

		$extra = $input['extra_field'] ?? array();
		if ( ! is_array( $extra ) ) {
			unset( $variant['extra_field'] );
			return $variant;
		}

		$type = sanitize_key( $extra['type'] ?? 'quantity' );
		if ( ! in_array( $type, self::EXTRA_FIELD_TYPES, true ) ) {
			$type = 'quantity';
		}

		$sanitized_extra = array(
			'type'     => $type,
			'label'    => sanitize_text_field( $extra['label'] ?? '' ),
			'required' => ! empty( $extra['required'] ),
		);

		// Quantity-specific options.
		if ( 'quantity' === $type ) {
			$sanitized_extra['min']            = max( 0, absint( $extra['min'] ?? 1 ) );
			$sanitized_extra['max']            = max( 1, absint( $extra['max'] ?? 10 ) );
			$sanitized_extra['multiply_price'] = ! empty( $extra['multiply_price'] );

			// Ensure min <= max.
			if ( $sanitized_extra['min'] > $sanitized_extra['max'] ) {
				$sanitized_extra['min'] = $sanitized_extra['max'];
			}
		}

		$variant['extra_field'] = $sanitized_extra;

		return $variant;
	}

	/**
	 * Get extra field configuration for a variant.
	 *
	 * @param int $kurs_id    Course ID.
	 * @param int $variant_id Variant ID.
	 * @return array<string, mixed>|null Extra field config or null.
	 */
	public static function get_variant_extra_field( int $kurs_id, int $variant_id ): ?array {
		if ( ! class_exists( 'Kurs_Booking_Kurs' ) ) {
			return null;
		}

		$varianten = Kurs_Booking_Kurs::get_varianten( $kurs_id );

		foreach ( $varianten as $variant ) {
			if ( isset( $variant['id'] ) && (int) $variant['id'] === $variant_id ) {
				return $variant['extra_field'] ?? null;
			}
		}

		return null;
	}

	// ═══════════════════════════════════════════════════════════════════════.
	// FRONTEND INTEGRATION.
	// ═══════════════════════════════════════════════════════════════════════.

	/**
	 * Render extra fields for Step 1 (ticket selection).
	 *
	 * Called from booking-form.php after each variant row.
	 *
	 * @param int                              $kurs_id   Course ID.
	 * @param array<int, array<string, mixed>> $varianten All variants.
	 */
	public static function render_extra_fields_step1( int $kurs_id, array $varianten ): void {
		// This is called per-variant in the booking form.
		// Each variant with extra_field gets an additional input.
		$template_path = KURS_BOOKING_PATH . 'templates/partials/booking-extra-fields.php';
		if ( file_exists( $template_path ) ) {
			include $template_path;
		}
	}

	/**
	 * Render a single variant's extra field input.
	 *
	 * @param array<string, mixed> $variant Variant with extra_field config.
	 */
	public static function render_variant_extra_input( array $variant ): void {
		$extra = $variant['extra_field'] ?? null;
		if ( empty( $extra ) ) {
			return;
		}

		$variant_id = $variant['id'] ?? 0;
		$type       = $extra['type'] ?? 'quantity';
		$label      = $extra['label'] ?? '';
		$required   = ! empty( $extra['required'] );
		?>
		<div class="kb-extra-field-input" data-variant-id="<?php echo esc_attr( (string) $variant_id ); ?>">
			<label class="kb-extra-field-label">
				<?php echo esc_html( $label ); ?>
				<?php if ( $required ) : ?>
					<span class="kb-required">*</span>
				<?php endif; ?>
			</label>

			<?php if ( 'quantity' === $type ) : ?>
				<input type="number"
					   name="extra_fields[<?php echo esc_attr( (string) $variant_id ); ?>]"
					   class="kb-extra-quantity"
					   min="<?php echo esc_attr( (string) ( $extra['min'] ?? 1 ) ); ?>"
					   max="<?php echo esc_attr( (string) ( $extra['max'] ?? 10 ) ); ?>"
					   value="<?php echo esc_attr( (string) ( $extra['min'] ?? 1 ) ); ?>"
					   <?php echo $required ? 'required' : ''; ?>
					   data-multiply="<?php echo ! empty( $extra['multiply_price'] ) ? '1' : '0'; ?>"
				/>
			<?php else : ?>
				<input type="text"
					   name="extra_fields[<?php echo esc_attr( (string) $variant_id ); ?>]"
					   class="kb-extra-text"
					   <?php echo $required ? 'required' : ''; ?>
				/>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Render course-specific custom fields for Step 2 (customer data).
	 *
	 * @param int $kurs_id Course ID.
	 */
	public static function render_custom_fields_step2( int $kurs_id ): void {
		$merged_fields = self::get_merged_fields( $kurs_id );

		if ( empty( $merged_fields ) ) {
			return;
		}

		// Render merged fields - can use same field renderer as booking-form.php.
		foreach ( $merged_fields as $field ) {
			self::render_booking_field( $field );
		}
	}

	/**
	 * Render a single booking field.
	 *
	 * @param array<string, mixed> $field Field configuration.
	 */
	private static function render_booking_field( array $field ): void {
		$type        = $field['type'] ?? 'text';
		$label       = $field['label'] ?? '';
		$placeholder = $field['placeholder'] ?? '';
		$mandatory   = ! empty( $field['mandatory'] );
		$field_id    = $field['id'] ?? sanitize_key( $label );
		$field_name  = 'custom_fields[' . esc_attr( $field_id ) . ']';

		?>
		<div class="buchung-field kb-merged-field" data-field-source="<?php echo esc_attr( $field['source'] ?? 'unknown' ); ?>">
			<label for="<?php echo esc_attr( $field_id ); ?>">
				<?php echo esc_html( $label ); ?>
				<?php if ( $mandatory ) : ?>
					<span class="kb-required">*</span>
				<?php endif; ?>
			</label>

			<?php
			switch ( $type ) {
				case 'textarea':
					?>
					<textarea id="<?php echo esc_attr( $field_id ); ?>"
							  name="<?php echo esc_attr( $field_name ); ?>"
							  placeholder="<?php echo esc_attr( $placeholder ); ?>"
							  <?php echo $mandatory ? 'required' : ''; ?>
					></textarea>
					<?php
					break;

				case 'select':
					?>
					<select id="<?php echo esc_attr( $field_id ); ?>"
							name="<?php echo esc_attr( $field_name ); ?>"
							<?php echo $mandatory ? 'required' : ''; ?>
					>
						<option value=""><?php esc_html_e( 'Bitte waehlen', 'kurs-booking' ); ?></option>
						<?php foreach ( ( $field['options'] ?? array() ) as $option ) : ?>
							<option value="<?php echo esc_attr( $option ); ?>">
								<?php echo esc_html( $option ); ?>
							</option>
						<?php endforeach; ?>
					</select>
					<?php
					break;

				case 'radio':
					?>
					<div class="kb-radio-group">
						<?php foreach ( ( $field['options'] ?? array() ) as $i => $option ) : ?>
							<label>
								<input type="radio"
									   name="<?php echo esc_attr( $field_name ); ?>"
									   value="<?php echo esc_attr( $option ); ?>"
									   <?php echo $mandatory && 0 === $i ? 'required' : ''; ?>
								/>
								<?php echo esc_html( $option ); ?>
							</label>
						<?php endforeach; ?>
					</div>
					<?php
					break;

				case 'checkbox':
					?>
					<input type="checkbox"
						   id="<?php echo esc_attr( $field_id ); ?>"
						   name="<?php echo esc_attr( $field_name ); ?>"
						   value="1"
						   <?php echo $mandatory ? 'required' : ''; ?>
					/>
					<?php
					break;

				case 'number':
					?>
					<input type="number"
						   id="<?php echo esc_attr( $field_id ); ?>"
						   name="<?php echo esc_attr( $field_name ); ?>"
						   placeholder="<?php echo esc_attr( $placeholder ); ?>"
						   <?php echo $mandatory ? 'required' : ''; ?>
					/>
					<?php
					break;

				default:
					?>
					<input type="<?php echo esc_attr( $type ); ?>"
						   id="<?php echo esc_attr( $field_id ); ?>"
						   name="<?php echo esc_attr( $field_name ); ?>"
						   placeholder="<?php echo esc_attr( $placeholder ); ?>"
						   <?php echo $mandatory ? 'required' : ''; ?>
					/>
					<?php
			}
			?>
		</div>
		<?php
	}

	// ═══════════════════════════════════════════════════════════════════════.
	// VALIDIERUNG & SPEICHERUNG.
	// ═══════════════════════════════════════════════════════════════════════.

	/**
	 * Validate extra fields from booking submission.
	 *
	 * @param int                  $kurs_id Course ID.
	 * @param array<string, mixed> $data    POST data.
	 * @return true|WP_Error True on success, WP_Error on validation failure.
	 */
	public static function validate_extra_fields( int $kurs_id, array $data ): true|WP_Error {
		$errors = array();

		// Validate merged custom fields.
		$merged_fields = self::get_merged_fields( $kurs_id );
		$custom_fields = $data['custom_fields'] ?? array();

		foreach ( $merged_fields as $field ) {
			$field_id = $field['id'] ?? '';
			$value    = $custom_fields[ $field_id ] ?? '';

			if ( ! empty( $field['mandatory'] ) && empty( $value ) ) {
				$errors[] = sprintf(
					/* translators: %s: field label */
					__( 'Das Feld "%s" ist erforderlich.', 'kurs-booking' ),
					$field['label'] ?? $field_id
				);
			}
		}

		// Validate variant extra fields.
		$extra_fields = $data['extra_fields'] ?? array();
		$tickets      = $data['tickets'] ?? array();

		if ( class_exists( 'Kurs_Booking_Kurs' ) ) {
			$varianten = Kurs_Booking_Kurs::get_varianten( $kurs_id );

			foreach ( $varianten as $variant ) {
				$variant_id = $variant['id'] ?? 0;
				$extra      = $variant['extra_field'] ?? null;

				// Skip if no extra field or no tickets booked for this variant.
				$ticket_count = absint( $tickets[ $variant_id ] ?? 0 );
				if ( empty( $extra ) || 0 === $ticket_count ) {
					continue;
				}

				$value = $extra_fields[ $variant_id ] ?? '';

				if ( ! empty( $extra['required'] ) && empty( $value ) ) {
					$errors[] = sprintf(
						/* translators: %s: field label */
						__( 'Das Feld "%s" ist erforderlich.', 'kurs-booking' ),
						$extra['label'] ?? ''
					);
				}

				// Validate quantity range.
				if ( 'quantity' === ( $extra['type'] ?? '' ) && '' !== $value ) {
					$qty = absint( $value );
					$min = absint( $extra['min'] ?? 1 );
					$max = absint( $extra['max'] ?? 10 );

					if ( $qty < $min || $qty > $max ) {
						$errors[] = sprintf(
							/* translators: %1$s: field label, %2$d: min, %3$d: max */
							__( '"%1$s" muss zwischen %2$d und %3$d liegen.', 'kurs-booking' ),
							$extra['label'] ?? '',
							$min,
							$max
						);
					}
				}
			}
		}

		if ( ! empty( $errors ) ) {
			return new WP_Error( 'validation_error', implode( ' ', $errors ) );
		}

		return true;
	}

	/**
	 * Calculate price multiplier from extra field.
	 *
	 * @param array<string, mixed> $variant Variant config with extra_field.
	 * @param array<string, mixed> $data    POST data with extra_fields values.
	 * @return float Multiplier (1.0 if no multiplication).
	 */
	public static function calculate_extra_multiplier( array $variant, array $data ): float {
		$extra = $variant['extra_field'] ?? null;
		if ( empty( $extra ) ) {
			return 1.0;
		}

		// Only quantity fields with multiply_price can affect price.
		if ( 'quantity' !== ( $extra['type'] ?? '' ) || empty( $extra['multiply_price'] ) ) {
			return 1.0;
		}

		$variant_id   = $variant['id'] ?? 0;
		$extra_fields = $data['extra_fields'] ?? array();
		$quantity     = absint( $extra_fields[ $variant_id ] ?? 1 );

		return max( 1.0, (float) $quantity );
	}

	/**
	 * Save extra field data to booking.
	 *
	 * @param int                  $buchung_id Booking ID.
	 * @param array<string, mixed> $data       POST data.
	 */
	public static function save_to_booking( int $buchung_id, array $data ): void {
		$kurs_id = absint( $data['kurs_id'] ?? 0 );
		if ( 0 === $kurs_id ) {
			return;
		}

		$stored = array(
			'custom_fields'  => array(),
			'variant_fields' => array(),
		);

		// Store merged custom field values.
		$merged_fields = self::get_merged_fields( $kurs_id );
		$custom_values = $data['custom_fields'] ?? array();

		foreach ( $merged_fields as $field ) {
			$field_id = $field['id'] ?? '';
			$value    = $custom_values[ $field_id ] ?? '';

			if ( '' !== $value ) {
				$stored['custom_fields'][ $field_id ] = array(
					'label'  => $field['label'] ?? '',
					'value'  => $value,
					'source' => $field['source'] ?? 'unknown',
				);
			}
		}

		// Store variant extra field values.
		$extra_values = $data['extra_fields'] ?? array();
		$tickets      = $data['tickets'] ?? array();

		if ( class_exists( 'Kurs_Booking_Kurs' ) ) {
			$varianten = Kurs_Booking_Kurs::get_varianten( $kurs_id );

			foreach ( $varianten as $index => $variant ) {
				$variant_id   = $variant['id'] ?? ( $index + 1 );
				$extra        = $variant['extra_field'] ?? null;
				$ticket_count = absint( $tickets[ $variant_id ] ?? 0 );

				if ( empty( $extra ) || 0 === $ticket_count ) {
					continue;
				}

				$value = $extra_values[ $variant_id ] ?? '';
				if ( '' !== $value ) {
					$stored['variant_fields'][ $variant_id ] = array(
						'label'          => $extra['label'] ?? '',
						'value'          => $value,
						'type'           => $extra['type'] ?? 'text',
						'multiply_price' => ! empty( $extra['multiply_price'] ),
					);
				}
			}
		}

		update_post_meta( $buchung_id, self::META_BOOKING_EXTRA, wp_json_encode( $stored ) );
	}

	/**
	 * Get extra field data from booking.
	 *
	 * @param int $buchung_id Booking ID.
	 * @return array<string, mixed> Stored extra field data.
	 */
	public static function get_from_booking( int $buchung_id ): array {
		$json = get_post_meta( $buchung_id, self::META_BOOKING_EXTRA, true );
		if ( empty( $json ) ) {
			return array(
				'custom_fields'  => array(),
				'variant_fields' => array(),
			);
		}

		$data = json_decode( $json, true );
		return is_array( $data ) ? $data : array(
			'custom_fields'  => array(),
			'variant_fields' => array(),
		);
	}
}
