<?php
/**
 * Auto-reply form provider registry.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\AutoReplies;

use SUBIN\Forms\FormFieldRepository;
use SUBIN\Forms\FormRepository;

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

/**
 * Provides form and visitor-email-field choices for auto-reply rules.
 */
class AutoReplyFormProviderRegistry {
	/**
	 * Form repository.
	 *
	 * @var FormRepository
	 */
	private $forms;

	/**
	 * Field repository.
	 *
	 * @var FormFieldRepository
	 */
	private $fields;

	/**
	 * Constructor.
	 *
	 * @param FormRepository|null      $forms Forms.
	 * @param FormFieldRepository|null $fields Fields.
	 */
	public function __construct( ?FormRepository $forms = null, ?FormFieldRepository $fields = null ) {
		$this->forms  = $forms ?: new FormRepository();
		$this->fields = $fields ?: new FormFieldRepository();
	}

	/**
	 * Get available providers.
	 *
	 * @return array<int,array<string,mixed>>
	 */
	public function providers() {
		$providers = array();
		foreach ( $this->provider_labels() as $key => $label ) {
			$has_forms = AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM === $key || ! empty( $this->list_forms( $key ) );
			$providers[] = array(
				'key'       => $key,
				'label'     => $label,
				'available' => AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM === $key || $this->is_provider_detected( $key ),
				'forms'     => $has_forms,
			);
		}

		return $providers;
	}

	/**
	 * Get provider label.
	 *
	 * @param string $provider Provider key.
	 * @return string
	 */
	public function provider_label( $provider ) {
		$labels   = $this->provider_labels();
		$provider = sanitize_key( (string) $provider );

		return $labels[ $provider ] ?? __( 'Unknown provider', 'submission-inbox' );
	}

	/**
	 * List forms for a provider.
	 *
	 * @param string $provider Provider key.
	 * @return array<int,array<string,string>>
	 */
	public function list_forms( $provider ) {
		$provider = sanitize_key( (string) $provider );
		if ( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM !== $provider ) {
			return $this->list_external_forms( $provider );
		}

		$forms = $this->forms->get_paginated(
			array(
				'per_page'       => 1000,
				'offset'         => 0,
				'exclude_status' => FormRepository::STATUS_TRASHED,
			)
		);

		$choices = array();
		foreach ( $forms as $form ) {
			$form_id = absint( $form['id'] ?? 0 );
			if ( $form_id <= 0 ) {
				continue;
			}

			$choices[] = array(
				'id'    => (string) $form_id,
				'label' => sanitize_text_field( (string) ( $form['title'] ?? '' ) ),
			);
		}

		return $choices;
	}

	/**
	 * Get one form choice.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<string,string>|null
	 */
	public function get_form( $provider, $form_id ) {
		$provider = sanitize_key( (string) $provider );
		$form_id  = sanitize_text_field( (string) $form_id );
		if ( '' === $form_id ) {
			return null;
		}

		if ( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM !== $provider ) {
			foreach ( $this->list_external_forms( $provider ) as $form ) {
				if ( $form_id === (string) $form['id'] ) {
					return $form;
				}
			}

			return null;
		}

		$form = $this->forms->get( absint( $form_id ) );
		if ( ! $form ) {
			return null;
		}

		return array(
			'id'    => (string) absint( $form['id'] ?? 0 ),
			'label' => sanitize_text_field( (string) ( $form['title'] ?? '' ) ),
		);
	}

	/**
	 * List visitor email fields for a form.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	public function list_email_fields( $provider, $form_id ) {
		$choices = array();
		foreach ( $this->list_fields( $provider, $form_id ) as $field ) {
			if ( 'email' !== ( $field['type'] ?? '' ) ) {
				continue;
			}
			$choices[] = array(
				'key'   => $field['key'],
				'label' => $field['label'],
			);
		}

		return $choices;
	}

	/**
	 * List condition-capable fields for one form.
	 *
	 * Every provider-specific schema is normalized to the types understood by
	 * AutoReplyConditionEngine. Unsupported, structural, payment, upload, and
	 * security fields are deliberately omitted.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<int,array{key:string,label:string,type:string,options:array<int,array{label:string,value:string}>}>
	 */
	public function list_fields( $provider, $form_id ) {
		$provider = sanitize_key( (string) $provider );
		$form_id  = sanitize_text_field( (string) $form_id );
		if ( '' === $form_id ) {
			return array();
		}

		if ( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM === $provider ) {
			$fields = array();
			foreach ( $this->fields->get_by_form_id( absint( $form_id ) ) as $field ) {
				$normalized = $this->normalize_schema_field(
					$field['field_key'] ?? '',
					$field['field_label'] ?? '',
					$field['field_type'] ?? '',
					$field['field_options'] ?? array()
				);
				if ( null !== $normalized ) {
					$fields[] = $normalized;
				}
			}

			return $fields;
		}

		return $this->list_external_fields( $provider, $form_id );
	}

	/**
	 * Get and validate one condition-capable field.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @param string $field_key Field key.
	 * @return array{key:string,label:string,type:string,options:array<int,array{label:string,value:string}>}|null
	 */
	public function get_field( $provider, $form_id, $field_key ) {
		$field_key = sanitize_key( (string) $field_key );
		foreach ( $this->list_fields( $provider, $form_id ) as $field ) {
			if ( $field_key === $field['key'] ) {
				return $field;
			}
		}

		return null;
	}

	/**
	 * Validate a selected email field.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @param string $field_key Field key.
	 * @return array<string,string>|null
	 */
	public function get_email_field( $provider, $form_id, $field_key ) {
		$field_key = sanitize_key( (string) $field_key );
		foreach ( $this->list_email_fields( $provider, $form_id ) as $field ) {
			if ( $field_key === $field['key'] ) {
				return $field;
			}
		}

		return null;
	}

	/**
	 * Provider labels.
	 *
	 * @return array<string,string>
	 */
	private function provider_labels() {
		return array(
			'submission_inbox_form' => __( 'InboxMend Forms', 'submission-inbox' ),
			'contact_form_7'    => __( 'Contact Form 7', 'submission-inbox' ),
			'wpforms'           => __( 'WPForms', 'submission-inbox' ),
			'fluent_forms'      => __( 'Fluent Forms', 'submission-inbox' ),
			'forminator'        => __( 'Forminator', 'submission-inbox' ),
			'ninja_forms'       => __( 'Ninja Forms', 'submission-inbox' ),
		);
	}

	/**
	 * Detect installed third-party providers without hard dependencies.
	 *
	 * @param string $provider Provider key.
	 * @return bool
	 */
	private function is_provider_detected( $provider ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return defined( 'WPCF7_VERSION' ) || class_exists( 'WPCF7_ContactForm' );
			case 'wpforms':
				return function_exists( 'wpforms' ) || class_exists( 'WPForms' );
			case 'fluent_forms':
				return defined( 'FLUENTFORM' ) || function_exists( 'wpFluentForm' );
			case 'forminator':
				return defined( 'FORMINATOR_VERSION' ) || class_exists( 'Forminator' );
			case 'ninja_forms':
				return function_exists( 'Ninja_Forms' ) || class_exists( 'Ninja_Forms' );
		}

		return false;
	}

	/**
	 * List external provider forms when safe metadata is available.
	 *
	 * @param string $provider Provider key.
	 * @return array<int,array<string,string>>
	 */
	private function list_external_forms( $provider ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return $this->list_cf7_forms();
			case 'wpforms':
				return $this->list_post_type_forms( 'wpforms' );
			case 'fluent_forms':
				return $this->list_fluent_forms();
			case 'forminator':
				return $this->list_post_type_forms( 'forminator_forms' );
			case 'ninja_forms':
				return $this->list_ninja_forms();
		}

		return array();
	}

	/**
	 * List normalized condition fields for an external form provider.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<int,array{key:string,label:string,type:string,options:array<int,array{label:string,value:string}>}>
	 */
	private function list_external_fields( $provider, $form_id ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return $this->list_cf7_fields( $form_id );
			case 'wpforms':
				return $this->list_wpforms_fields( $form_id );
			case 'fluent_forms':
				return $this->list_fluent_fields( $form_id );
			case 'forminator':
				return $this->list_forminator_fields( $form_id );
			case 'ninja_forms':
				return $this->list_ninja_fields( $form_id );
		}

		return array();
	}

	/**
	 * List Contact Form 7 condition fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,mixed>>
	 */
	private function list_cf7_fields( $form_id ) {
		if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
			return array();
		}

		$form = \WPCF7_ContactForm::get_instance( absint( $form_id ) );
		if ( ! $form ) {
			return array();
		}

		$fields = array();
		foreach ( $form->scan_form_tags() as $tag ) {
			$normalized = $this->normalize_schema_field(
				$tag->name ?? '',
				$this->label_from_key( $tag->name ?? '' ),
				$tag->basetype ?? $tag->type ?? '',
				$tag->values ?? array()
			);
			if ( null !== $normalized ) {
				$fields[] = $normalized;
			}
		}

		return $this->unique_fields( $fields );
	}

	/**
	 * List WPForms condition fields using the same stable keys as capture.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,mixed>>
	 */
	private function list_wpforms_fields( $form_id ) {
		$post = get_post( absint( $form_id ) );
		if ( ! $post || 'wpforms' !== $post->post_type ) {
			return array();
		}

		$data   = json_decode( (string) $post->post_content, true );
		$config = is_array( $data['fields'] ?? null ) ? $data['fields'] : array();
		$fields = array();
		foreach ( $config as $field ) {
			if ( ! is_array( $field ) ) {
				continue;
			}
			$type = sanitize_key( (string) ( $field['type'] ?? '' ) );
			$id   = absint( $field['id'] ?? 0 );
			$key  = $id > 0
				? 'wpforms_field_' . $id . ( '' !== $type ? '_' . $type : '' )
				: sanitize_key( (string) ( $field['key'] ?? $field['name'] ?? $field['label'] ?? '' ) );
			$normalized = $this->normalize_schema_field(
				$key,
				$field['label'] ?? $field['name'] ?? '',
				$type,
				$field['choices'] ?? array()
			);
			if ( null !== $normalized ) {
				$fields[] = $normalized;
			}
		}

		return $this->unique_fields( $fields );
	}

	/**
	 * List Fluent Forms condition fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,mixed>>
	 */
	private function list_fluent_fields( $form_id ) {
		global $wpdb;

		$table = $wpdb->prefix . 'fluentform_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$json = $wpdb->get_var( $wpdb->prepare( "SELECT form_fields FROM {$table} WHERE id = %d", absint( $form_id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$data = is_string( $json ) ? json_decode( $json, true ) : null;
		if ( ! is_array( $data ) ) {
			return array();
		}

		$fields = array();
		$walker = function ( $node ) use ( &$walker, &$fields ) {
			if ( ! is_array( $node ) ) {
				return;
			}
			$key = sanitize_key( (string) ( $node['attributes']['name'] ?? '' ) );
			if ( '' !== $key ) {
				$type = $node['element'] ?? $node['attributes']['type'] ?? '';
				$normalized = $this->normalize_schema_field(
					$key,
					$node['settings']['admin_field_label'] ?? $node['settings']['label'] ?? '',
					$type,
					$node['settings']['advanced_options'] ?? $node['settings']['options'] ?? array()
				);
				if ( null !== $normalized ) {
					$fields[] = $normalized;
				}
			}
			foreach ( $node as $child ) {
				$walker( $child );
			}
		};
		$walker( $data );

		return $this->unique_fields( $fields );
	}

	/**
	 * List Forminator condition fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,mixed>>
	 */
	private function list_forminator_fields( $form_id ) {
		$meta   = get_post_meta( absint( $form_id ), 'forminator_form_meta', true );
		$data   = is_array( $meta ) ? $meta : maybe_unserialize( $meta );
		$config = is_array( $data['fields'] ?? null ) ? $data['fields'] : array();
		$fields = array();
		foreach ( $config as $field ) {
			if ( ! is_array( $field ) ) {
				continue;
			}
			$key = $field['element_id'] ?? $field['id'] ?? '';
			$normalized = $this->normalize_schema_field(
				$key,
				$field['field_label'] ?? $field['label'] ?? '',
				$field['type'] ?? $field['element'] ?? '',
				$field['options'] ?? array()
			);
			if ( null !== $normalized ) {
				$fields[] = $normalized;
			}
		}

		return $this->unique_fields( $fields );
	}

	/**
	 * List Ninja Forms condition fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,mixed>>
	 */
	private function list_ninja_fields( $form_id ) {
		if ( function_exists( 'Ninja_Forms' ) ) {
			try {
				$api    = \Ninja_Forms();
				$model  = is_object( $api ) && method_exists( $api, 'form' ) ? $api->form( absint( $form_id ) ) : null;
				$models = is_object( $model ) && method_exists( $model, 'get_fields' ) ? $model->get_fields() : array();
				$fields = array();
				foreach ( is_array( $models ) ? $models : array() as $field ) {
					if ( ! is_object( $field ) || ! method_exists( $field, 'get_setting' ) ) {
						continue;
					}
					$normalized = $this->normalize_schema_field(
						$field->get_setting( 'key' ),
						$field->get_setting( 'label' ),
						$field->get_setting( 'type' ),
						$field->get_setting( 'options' )
					);
					if ( null !== $normalized ) {
						$fields[] = $normalized;
					}
				}
				if ( ! empty( $fields ) ) {
					return $this->unique_fields( $fields );
				}
			} catch ( \Throwable $throwable ) {
				unset( $throwable );
			}
		}

		global $wpdb;

		$table = $wpdb->prefix . 'nf3_fields';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results(
			$wpdb->prepare( "SELECT `key`, label, type FROM {$table} WHERE parent_id = %d ORDER BY id ASC", absint( $form_id ) ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			ARRAY_A
		);
		$fields = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$normalized = $this->normalize_schema_field(
				$row['key'] ?? '',
				$row['label'] ?? '',
				$row['type'] ?? '',
				array()
			);
			if ( null !== $normalized ) {
				$fields[] = $normalized;
			}
		}

		return $this->unique_fields( $fields );
	}

	/**
	 * List external provider email fields.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_external_email_fields( $provider, $form_id ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return $this->list_cf7_email_fields( $form_id );
			case 'wpforms':
				return $this->list_wpforms_email_fields( $form_id );
			case 'fluent_forms':
				return $this->list_fluent_email_fields( $form_id );
			case 'forminator':
				return $this->list_forminator_email_fields( $form_id );
			case 'ninja_forms':
				return $this->list_ninja_email_fields( $form_id );
		}

		return array();
	}

	/**
	 * List forms stored as posts.
	 *
	 * @param string $post_type Post type.
	 * @return array<int,array<string,string>>
	 */
	private function list_post_type_forms( $post_type ) {
		$posts = get_posts(
			array(
				'post_type'      => sanitize_key( $post_type ),
				'post_status'    => 'any',
				'posts_per_page' => 500,
				'orderby'        => 'title',
				'order'          => 'ASC',
			)
		);

		$forms = array();
		foreach ( $posts as $post ) {
			$forms[] = array(
				'id'    => (string) absint( $post->ID ),
				'label' => sanitize_text_field( $post->post_title ),
			);
		}

		return $forms;
	}

	/**
	 * List Contact Form 7 forms.
	 *
	 * @return array<int,array<string,string>>
	 */
	private function list_cf7_forms() {
		if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
			return array();
		}

		$forms = array();
		foreach ( \WPCF7_ContactForm::find() as $form ) {
			$forms[] = array(
				'id'    => (string) absint( $form->id() ),
				'label' => sanitize_text_field( $form->title() ),
			);
		}

		return $forms;
	}

	/**
	 * List Contact Form 7 email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_cf7_email_fields( $form_id ) {
		if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
			return array();
		}

		$form = \WPCF7_ContactForm::get_instance( absint( $form_id ) );
		if ( ! $form ) {
			return array();
		}

		$fields = array();
		foreach ( $form->scan_form_tags() as $tag ) {
			$type = sanitize_key( $tag->basetype ?? $tag->type ?? '' );
			$name = sanitize_key( $tag->name ?? '' );
			if ( 'email' !== $type || '' === $name ) {
				continue;
			}
			$fields[] = array(
				'key'   => $name,
				'label' => $this->label_from_key( $name ),
			);
		}

		return $fields;
	}

	/**
	 * List Fluent Forms forms.
	 *
	 * @return array<int,array<string,string>>
	 */
	private function list_fluent_forms() {
		global $wpdb;

		$table = $wpdb->prefix . 'fluentform_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results( "SELECT id, title FROM {$table} ORDER BY title ASC, id ASC LIMIT 500", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$forms = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$forms[] = array(
				'id'    => (string) absint( $row['id'] ?? 0 ),
				'label' => sanitize_text_field( (string) ( $row['title'] ?? '' ) ),
			);
		}

		return $forms;
	}

	/**
	 * List Fluent Forms email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_fluent_email_fields( $form_id ) {
		global $wpdb;

		$table = $wpdb->prefix . 'fluentform_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$json = $wpdb->get_var( $wpdb->prepare( "SELECT form_fields FROM {$table} WHERE id = %d", absint( $form_id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$data = is_string( $json ) ? json_decode( $json, true ) : null;

		return is_array( $data ) ? $this->extract_fluent_email_fields( $data ) : array();
	}

	/**
	 * List Forminator email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_forminator_email_fields( $form_id ) {
		$meta = get_post_meta( absint( $form_id ), 'forminator_form_meta', true );
		$data = is_array( $meta ) ? $meta : maybe_unserialize( $meta );
		$fields = is_array( $data['fields'] ?? null ) ? $data['fields'] : array();
		$choices = array();

		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) || 'email' !== sanitize_key( $field['type'] ?? $field['element'] ?? '' ) ) {
				continue;
			}
			$key = sanitize_key( (string) ( $field['element_id'] ?? $field['id'] ?? '' ) );
			if ( '' === $key ) {
				continue;
			}
			$choices[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $field['field_label'] ?? $field['label'] ?? $this->label_from_key( $key ) ) ),
			);
		}

		return $choices;
	}

	/**
	 * List Ninja Forms forms.
	 *
	 * @return array<int,array<string,string>>
	 */
	private function list_ninja_forms() {
		global $wpdb;

		$table = $wpdb->prefix . 'nf3_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results( "SELECT id, title FROM {$table} ORDER BY title ASC, id ASC LIMIT 500", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$forms = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$forms[] = array(
				'id'    => (string) absint( $row['id'] ?? 0 ),
				'label' => sanitize_text_field( (string) ( $row['title'] ?? '' ) ),
			);
		}

		return $forms;
	}

	/**
	 * List Ninja Forms email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_ninja_email_fields( $form_id ) {
		global $wpdb;

		$table = $wpdb->prefix . 'nf3_fields';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results(
			$wpdb->prepare( "SELECT field_key, `key`, label FROM {$table} WHERE parent_id = %d AND type = %s ORDER BY id ASC", absint( $form_id ), 'email' ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			ARRAY_A
		);
		$fields = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$key = sanitize_key( (string) ( $row['field_key'] ?: $row['key'] ?? '' ) );
			if ( '' === $key ) {
				continue;
			}
			$fields[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $row['label'] ?: $this->label_from_key( $key ) ) ),
			);
		}

		return $fields;
	}

	/**
	 * List WPForms email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_wpforms_email_fields( $form_id ) {
		$post = get_post( absint( $form_id ) );
		if ( ! $post || 'wpforms' !== $post->post_type ) {
			return array();
		}

		$data = json_decode( (string) $post->post_content, true );
		$fields = is_array( $data['fields'] ?? null ) ? $data['fields'] : array();
		$choices = array();
		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) || 'email' !== sanitize_key( $field['type'] ?? '' ) ) {
				continue;
			}
			$id = absint( $field['id'] ?? 0 );
			$key = $id > 0 ? 'wpforms_field_' . $id . '_email' : sanitize_key( $field['key'] ?? $field['label'] ?? '' );
			if ( '' === $key ) {
				continue;
			}
			$choices[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $field['label'] ?? $this->label_from_key( $key ) ) ),
			);
		}

		return $choices;
	}

	/**
	 * Extract Fluent Forms email fields from nested field config.
	 *
	 * @param array<string,mixed> $data Form fields config.
	 * @return array<int,array<string,string>>
	 */
	private function extract_fluent_email_fields( array $data ) {
		$fields = array();
		$walker = function ( $node ) use ( &$walker, &$fields ) {
			if ( ! is_array( $node ) ) {
				return;
			}
			$element = sanitize_key( (string) ( $node['element'] ?? '' ) );
			$type    = sanitize_key( (string) ( $node['attributes']['type'] ?? '' ) );
			if ( 'input_email' === $element || 'email' === $type ) {
				$key = sanitize_key( (string) ( $node['attributes']['name'] ?? '' ) );
				if ( '' !== $key ) {
					$fields[ $key ] = array(
						'key'   => $key,
						'label' => sanitize_text_field( (string) ( $node['settings']['admin_field_label'] ?? $node['settings']['label'] ?? $this->label_from_key( $key ) ) ),
					);
				}
			}
			foreach ( $node as $child ) {
				$walker( $child );
			}
		};
		$walker( $data );

		return array_values( $fields );
	}

	/**
	 * Normalize a provider field into the public condition schema.
	 *
	 * @param mixed $key Field key.
	 * @param mixed $label Field label.
	 * @param mixed $type Provider field type.
	 * @param mixed $options Provider options.
	 * @return array{key:string,label:string,type:string,options:array<int,array{label:string,value:string}>}|null
	 */
	private function normalize_schema_field( $key, $label, $type, $options ) {
		$key  = sanitize_key( is_scalar( $key ) ? (string) $key : '' );
		$type = $this->condition_type( $type );
		if ( '' === $key || '' === $type ) {
			return null;
		}

		$label = sanitize_text_field( is_scalar( $label ) ? (string) $label : '' );
		return array(
			'key'     => $key,
			'label'   => '' !== $label ? $label : $this->label_from_key( $key ),
			'type'    => $type,
			'options' => $this->normalize_field_options( $options ),
		);
	}

	/**
	 * Map provider-native field types to condition-engine types.
	 *
	 * @param mixed $type Raw type.
	 * @return string
	 */
	private function condition_type( $type ) {
		$type = sanitize_key( is_scalar( $type ) ? (string) $type : '' );
		$map  = array(
			'text'            => 'text',
			'textbox'         => 'text',
			'input_text'      => 'text',
			'name'            => 'text',
			'address'         => 'text',
			'firstname'       => 'text',
			'lastname'        => 'text',
			'email'           => 'email',
			'input_email'     => 'email',
			'url'             => 'url',
			'website_url'     => 'url',
			'phone'           => 'tel',
			'tel'             => 'tel',
			'phone_number'    => 'tel',
			'input_mask'      => 'tel',
			'textarea'        => 'textarea',
			'choice'          => 'choice',
			'select'          => 'select',
			'dropdown'        => 'select',
			'listselect'      => 'select',
			'multi_select'    => 'select',
			'radio'           => 'radio',
			'input_radio'     => 'radio',
			'listradio'       => 'radio',
			'checkbox'        => 'checkbox',
			'input_checkbox'  => 'checkbox',
			'listcheckbox'    => 'checkbox',
			'gdpr_checkbox'   => 'consent',
			'gdpr-checkbox'   => 'consent',
			'consent'         => 'consent',
			'acceptance'      => 'consent',
			'number'          => 'number',
			'numbers'         => 'number',
			'input_number'    => 'number',
			'numeric'         => 'number',
			'quantity'        => 'number',
			'rating'          => 'number',
			'range'           => 'number',
			'date'            => 'date',
			'datepicker'      => 'date',
			'datetime'        => 'datetime',
			'date_time'       => 'datetime',
			'date-time'       => 'datetime',
			'time'            => 'time',
			'timepicker'      => 'time',
		);

		return $map[ $type ] ?? '';
	}

	/**
	 * Normalize selectable field options.
	 *
	 * @param mixed $options Raw options or encoded JSON.
	 * @return array<int,array{label:string,value:string}>
	 */
	private function normalize_field_options( $options ) {
		if ( is_string( $options ) ) {
			$decoded = json_decode( $options, true );
			$options = is_array( $decoded ) ? $decoded : array();
		}
		if ( ! is_array( $options ) ) {
			return array();
		}

		$normalized = array();
		foreach ( array_slice( $options, 0, 200 ) as $key => $option ) {
			if ( is_scalar( $option ) ) {
				$label = sanitize_text_field( (string) $option );
				$value = $label;
			} elseif ( is_array( $option ) ) {
				$label = sanitize_text_field( (string) ( $option['label'] ?? $option['name'] ?? $option['value'] ?? '' ) );
				$value = sanitize_text_field( (string) ( $option['value'] ?? $option['key'] ?? ( is_string( $key ) ? $key : $label ) ) );
			} else {
				continue;
			}
			if ( '' === $label || '' === $value ) {
				continue;
			}
			$normalized[ $value ] = array(
				'label' => $label,
				'value' => $value,
			);
		}

		return array_values( $normalized );
	}

	/**
	 * De-duplicate provider schema fields while preserving display order.
	 *
	 * @param array<int,array<string,mixed>> $fields Fields.
	 * @return array<int,array<string,mixed>>
	 */
	private function unique_fields( array $fields ) {
		$unique = array();
		foreach ( $fields as $field ) {
			$key = sanitize_key( (string) ( $field['key'] ?? '' ) );
			if ( '' !== $key && ! isset( $unique[ $key ] ) ) {
				$unique[ $key ] = $field;
			}
		}

		return array_values( $unique );
	}

	/**
	 * Check whether a custom table exists.
	 *
	 * @param string $table Table name.
	 * @return bool
	 */
	private function table_exists( $table ) {
		global $wpdb;

		return (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table;
	}

	/**
	 * Build a readable label from a field key.
	 *
	 * @param string $key Field key.
	 * @return string
	 */
	private function label_from_key( $key ) {
		return ucwords( str_replace( array( '-', '_' ), ' ', sanitize_key( (string) $key ) ) );
	}
}
