<?php
/** Shared Formidable Forms model adapter helpers. */

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

define( 'WPAGENT_COMPANION_OPT_FORMIDABLE_GRANTS', 'wp_agent_companion_formidable_capability_grants' );
define( 'WPAGENT_COMPANION_OPT_FORMIDABLE_LOCK', 'wp_agent_companion_formidable_mutation_lock' );
define( 'WPAGENT_COMPANION_FORMIDABLE_LOCK_TTL', 600 );

/** @return string[] */
function wpagent_formidable_required_capabilities(): array {
	return array( 'frm_view_forms', 'frm_edit_forms', 'frm_delete_forms' );
}

/** @return array<int,string[]> */
function wpagent_formidable_capability_grants(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_FORMIDABLE_GRANTS, array() );
	$grants = array();
	foreach ( is_array( $stored ) ? $stored : array() as $user_id => $capabilities ) {
		$user_id = absint( $user_id );
		if ( ! $user_id || ! is_array( $capabilities ) ) {
			continue;
		}
		$allowed = array_values( array_intersect( wpagent_formidable_required_capabilities(), array_map( 'sanitize_key', $capabilities ) ) );
		if ( $allowed ) {
			$grants[ $user_id ] = $allowed;
		}
	}
	return array_slice( $grants, 0, 200, true );
}

/** @param array<int,string[]> $grants */
function wpagent_formidable_store_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_FORMIDABLE_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_FORMIDABLE_GRANTS );
	}
	return wpagent_formidable_capability_grants() === $grants;
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_formidable_user_access( int $user_id ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_formidable_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capabilities = array_values(
		array_filter(
			wpagent_formidable_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_formidable_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_formidable_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_formidable_set_user_access( int $user_id, bool $enabled ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_formidable_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $enabled && ! wpagent_formidable_available() ) {
		return wpagent_formidable_missing_error();
	}
	$lock = wpagent_companion_acquire_connection_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}

	try {
		$grants          = wpagent_formidable_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		$newly_added     = array();
		if ( $enabled ) {
			foreach ( wpagent_formidable_required_capabilities() as $capability ) {
				if ( in_array( $capability, $tracked, true ) ) {
					$user->add_cap( $capability );
					continue;
				}
				if ( ! user_can( $user, $capability ) ) {
					$user->add_cap( $capability );
					$tracked[]     = $capability;
					$newly_added[] = $capability;
				}
			}
			$tracked = array_values( array_unique( $tracked ) );
			if ( $tracked ) {
				$grants[ $user_id ] = $tracked;
			} else {
				unset( $grants[ $user_id ] );
			}
			if ( ! wpagent_formidable_store_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_formidable_store_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_formidable_grant_store_failed', __( 'Die Formidable-Forms-Rechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		} else {
			foreach ( $tracked as $capability ) {
				$user->remove_cap( $capability );
			}
			clean_user_cache( $user_id );
			$fresh_user = get_user_by( 'id', $user_id );
			$remaining  = $fresh_user instanceof WP_User
				? array_values( array_intersect( $tracked, array_keys( array_filter( (array) $fresh_user->caps ) ) ) )
				: $tracked;
			if ( $remaining ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_formidable_grant_remove_failed', __( 'Die von WPAgently ergänzten Formidable-Forms-Rechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_formidable_store_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_formidable_grant_store_failed', __( 'Die Formidable-Forms-Rechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_formidable_user_access( $user_id );
		if ( $enabled && ( is_wp_error( $access ) || ! $access['enabled'] ) ) {
			foreach ( $newly_added as $capability ) {
				$user->remove_cap( $capability );
			}
			wpagent_formidable_store_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_formidable_grant_failed', __( 'Die Formidable-Forms-Rechte konnten nicht vollständig aktiviert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $access;
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

function wpagent_formidable_available(): bool {
	return class_exists( 'FrmForm' )
		&& class_exists( 'FrmField' )
		&& is_callable( array( 'FrmForm', 'getOne' ) )
		&& is_callable( array( 'FrmForm', 'getAll' ) )
		&& is_callable( array( 'FrmForm', 'create' ) )
		&& is_callable( array( 'FrmForm', 'update' ) )
		&& is_callable( array( 'FrmForm', 'duplicate' ) )
		&& is_callable( array( 'FrmForm', 'destroy' ) )
		&& is_callable( array( 'FrmField', 'getOne' ) )
		&& is_callable( array( 'FrmField', 'get_all_for_form' ) )
		&& is_callable( array( 'FrmField', 'create' ) )
		&& is_callable( array( 'FrmField', 'update' ) )
		&& is_callable( array( 'FrmField', 'destroy' ) );
}

function wpagent_formidable_missing_error(): WP_Error {
	return new WP_Error(
		'wpagent_formidable_forms_missing',
		__( 'Formidable Forms ist nicht aktiv oder seine native Modell-API ist nicht verfügbar.', 'wp-agent-companion' ),
		array( 'status' => 400 )
	);
}

function wpagent_formidable_provider_error(): WP_Error {
	return new WP_Error(
		'wpagent_formidable_provider_failed',
		__( 'Formidable Forms konnte die angeforderten Formulardaten nicht zuverlässig lesen.', 'wp-agent-companion' ),
		array( 'status' => 500 )
	);
}

function wpagent_formidable_payload_limit_error(): WP_Error {
	return new WP_Error(
		'wpagent_formidable_payload_too_large',
		__( 'Das Formidable-Forms-Formular überschreitet die sicheren Grenzen für Feldanzahl, Auswahlwerte oder Textlängen.', 'wp-agent-companion' ),
		array( 'status' => 413 )
	);
}

/** @param mixed $input */
function wpagent_perm_formidable_view( $input = null ): bool {
	return wpagent_formidable_available() && current_user_can( 'frm_view_forms' );
}

/** @param mixed $input */
function wpagent_perm_formidable_edit( $input = null ): bool {
	return wpagent_formidable_available() && current_user_can( 'frm_edit_forms' );
}

/** @param mixed $input */
function wpagent_perm_formidable_delete( $input = null ): bool {
	return wpagent_formidable_available() && current_user_can( 'frm_delete_forms' );
}

/** @return string|WP_Error */
function wpagent_formidable_acquire_mutation_lock() {
	$token = wpagent_companion_acquire_option_lock( WPAGENT_COMPANION_OPT_FORMIDABLE_LOCK, WPAGENT_COMPANION_FORMIDABLE_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_formidable_locked', __( 'Eine andere Formidable-Forms-Änderung läuft bereits. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_formidable_release_mutation_lock( string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_COMPANION_OPT_FORMIDABLE_LOCK, $token );
}

/** @param mixed $value @return mixed */
function wpagent_formidable_json_value( $value ) {
	$encoded = wp_json_encode( $value );
	return false === $encoded ? null : json_decode( $encoded, true );
}

/** @param mixed $value @return mixed */
function wpagent_formidable_plain_value( $value, int $depth = 0 ) {
	if ( is_array( $value ) ) {
		if ( $depth >= 5 || count( $value ) > 500 ) {
			throw new OverflowException( 'Formidable Forms value exceeds the safe response boundary.' );
		}
		$clean = array();
		foreach ( $value as $key => $item ) {
			$clean[ $key ] = wpagent_formidable_plain_value( $item, $depth + 1 );
		}
		return $clean;
	}
	if ( ! is_string( $value ) ) {
		return $value;
	}
	$value  = sanitize_textarea_field( $value );
	$length = function_exists( 'mb_strlen' ) ? mb_strlen( $value ) : strlen( $value );
	if ( $length > 10000 ) {
		throw new OverflowException( 'Formidable Forms text exceeds the safe response boundary.' );
	}
	return $value;
}

function wpagent_formidable_bounded_text( string $value, int $max_length, bool $multiline = true ): string {
	$value  = $multiline ? sanitize_textarea_field( $value ) : sanitize_text_field( $value );
	$length = function_exists( 'mb_strlen' ) ? mb_strlen( $value ) : strlen( $value );
	if ( $length > $max_length ) {
		throw new OverflowException( 'Formidable Forms text exceeds the safe response boundary.' );
	}
	return $value;
}

/**
 * Keeps choice data useful without exposing arbitrary provider or add-on keys.
 *
 * @param mixed $options
 * @return array<mixed>
 */
function wpagent_formidable_safe_options( $options ): array {
	if ( ! is_array( $options ) || ! array_is_list( $options ) ) {
		return array();
	}
	if ( count( $options ) > 500 ) {
		throw new OverflowException( 'Formidable Forms choices exceed the safe response boundary.' );
	}
	$safe = array();
	foreach ( $options as $key => $option ) {
		if ( is_array( $option ) || is_object( $option ) ) {
			$data  = is_object( $option ) ? get_object_vars( $option ) : $option;
			$value = array();
			foreach ( array( 'label', 'value' ) as $property ) {
				if ( array_key_exists( $property, $data ) ) {
					$value[ $property ] = wpagent_formidable_bounded_text( (string) $data[ $property ], 10000 );
				}
			}
			if ( array_key_exists( 'image', $data ) ) {
				$value['image'] = esc_url_raw( (string) $data['image'], array( 'http', 'https' ) );
			}
			$safe[ $key ] = $value;
			continue;
		}
		$safe[ $key ] = is_string( $option ) ? wpagent_formidable_bounded_text( $option, 10000 ) : $option;
	}
	return $safe;
}

/** @param mixed $options @return array<string,mixed> */
function wpagent_formidable_safe_field_options( $options ): array {
	$options = is_array( $options ) ? $options : array();
	$safe    = array();
	foreach ( array( 'rows', 'size', 'limit', 'max_char' ) as $key ) {
		if ( array_key_exists( $key, $options ) && is_numeric( $options[ $key ] ) ) {
			$safe[ $key ] = min( 1000000, max( 0, (int) $options[ $key ] ) );
		}
	}
	foreach ( array( 'max', 'min', 'step' ) as $key ) {
		if ( array_key_exists( $key, $options ) && is_numeric( $options[ $key ] ) ) {
			$safe[ $key ] = (float) $options[ $key ];
		}
	}
	foreach ( array( 'read_only', 'unique', 'clear_on_focus', 'image_options', 'separate_value', 'multiple' ) as $key ) {
		if ( array_key_exists( $key, $options ) && is_scalar( $options[ $key ] ) ) {
			$safe[ $key ] = (bool) $options[ $key ];
		}
	}
	foreach ( array( 'placeholder', 'format', 'invalid', 'classes', 'autocomplete', 'align', 'visibility' ) as $key ) {
		if ( array_key_exists( $key, $options ) && is_scalar( $options[ $key ] ) ) {
			$safe[ $key ] = wpagent_formidable_bounded_text( (string) $options[ $key ], 5000 );
		}
	}
	return $safe;
}

function wpagent_formidable_sensitive_field_type( string $type ): bool {
	return in_array( sanitize_key( $type ), array( 'hidden', 'password' ), true );
}

/** @param mixed $field @return array<string,mixed> */
function wpagent_formidable_field_payload( $field ): array {
	$data = is_object( $field ) ? get_object_vars( $field ) : ( is_array( $field ) ? $field : array() );
	$type = sanitize_key( (string) ( $data['type'] ?? 'text' ) );
	return array(
		'id'            => absint( $data['id'] ?? 0 ),
		'field_key'     => sanitize_key( (string) ( $data['field_key'] ?? '' ) ),
		'name'          => wpagent_formidable_bounded_text( (string) ( $data['name'] ?? '' ), 255, false ),
		'description'   => wpagent_formidable_bounded_text( (string) ( $data['description'] ?? '' ), 5000 ),
		'type'          => $type,
		'default_value' => wpagent_formidable_sensitive_field_type( $type ) ? null : wpagent_formidable_plain_value( wpagent_formidable_json_value( $data['default_value'] ?? '' ) ),
		'options'       => wpagent_formidable_safe_options( wpagent_formidable_json_value( $data['options'] ?? array() ) ),
		'field_order'   => (int) ( $data['field_order'] ?? 0 ),
		'required'      => ! empty( $data['required'] ),
		'field_options' => wpagent_formidable_safe_field_options( $data['field_options'] ?? array() ),
	);
}

/** @param mixed $form @return array<string,mixed> */
function wpagent_formidable_form_payload( $form, bool $include_fields = true ): array {
	$data   = is_object( $form ) ? get_object_vars( $form ) : ( is_array( $form ) ? $form : array() );
	$id     = absint( $data['id'] ?? 0 );
	$fields = array();
	if ( $include_fields && $id > 0 && wpagent_formidable_available() ) {
		foreach ( wpagent_formidable_bounded_fields( $id ) as $field ) {
			$fields[] = wpagent_formidable_field_payload( $field );
		}
	}
	return array(
		'provider'    => 'formidable-forms',
		'id'          => $id,
		'form_key'    => sanitize_key( (string) ( $data['form_key'] ?? '' ) ),
		'name'        => wpagent_formidable_bounded_text( (string) ( $data['name'] ?? '' ), 255, false ),
		'description' => wpagent_formidable_bounded_text( (string) ( $data['description'] ?? '' ), 10000 ),
		'status'      => sanitize_key( (string) ( $data['status'] ?? '' ) ),
		'created_at'  => sanitize_text_field( (string) ( $data['created_at'] ?? '' ) ),
		'updated_at'  => sanitize_text_field( (string) ( $data['updated_at'] ?? '' ) ),
		'shortcode'   => sprintf( '[formidable id="%d"]', $id ),
		'fields'      => $fields,
	);
}

/** @return array<int,mixed> */
function wpagent_formidable_bounded_fields( int $form_id ): array {
	$fields = (array) FrmField::get_all_for_form( $form_id, 501, 'exclude', 'include' );
	if ( count( $fields ) > 500 ) {
		throw new OverflowException( 'Formidable Forms field count exceeds the safe response boundary.' );
	}
	return $fields;
}

/** @param mixed $input @return object|WP_Error */
function wpagent_formidable_get_authorized_form( $input, string $capability ) {
	if ( ! wpagent_formidable_available() ) {
		return wpagent_formidable_missing_error();
	}
	$input   = is_array( $input ) ? $input : array();
	$form_id = absint( $input['form_id'] ?? 0 );
	if ( $form_id <= 0 ) {
		return new WP_Error( 'wpagent_bad_form_id', __( 'form_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! current_user_can( $capability ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für dieses Formidable-Forms-Formular.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	try {
		$form = FrmForm::getOne( $form_id );
	} catch ( Throwable $error ) {
		return wpagent_formidable_provider_error();
	}
	if ( ! is_object( $form ) ) {
		return new WP_Error( 'wpagent_formidable_form_not_found', __( 'Formidable-Forms-Formular nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return $form;
}

/** @return array<string,mixed> */
function wpagent_formidable_form_output_schema(): array {
	return array(
		'type'       => 'object',
		'properties' => array(
			'provider' => array( 'type' => 'string' ), 'id' => array( 'type' => 'integer' ),
			'form_key' => array( 'type' => 'string' ), 'name' => array( 'type' => 'string' ),
			'description' => array( 'type' => 'string' ), 'status' => array( 'type' => 'string' ),
			'created_at' => array( 'type' => 'string' ), 'updated_at' => array( 'type' => 'string' ),
			'shortcode' => array( 'type' => 'string' ),
			'fields' => array( 'type' => 'array', 'items' => array( 'type' => 'object' ) ),
		),
	);
}

/** @return array<string,mixed> */
function wpagent_formidable_field_options_schema(): array {
	$properties = array();
	foreach ( array( 'rows', 'size', 'limit', 'max_char' ) as $key ) {
		$properties[ $key ] = array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 1000000 );
	}
	foreach ( array( 'max', 'min', 'step' ) as $key ) {
		$properties[ $key ] = array( 'type' => 'number' );
	}
	foreach ( array( 'read_only', 'unique', 'clear_on_focus', 'image_options', 'separate_value', 'multiple' ) as $key ) {
		$properties[ $key ] = array( 'type' => 'boolean' );
	}
	foreach ( array( 'placeholder', 'format', 'invalid', 'classes', 'autocomplete', 'align', 'visibility' ) as $key ) {
		$properties[ $key ] = array( 'type' => 'string', 'maxLength' => 5000 );
	}
	return array( 'type' => 'object', 'additionalProperties' => false, 'maxProperties' => 21, 'properties' => $properties );
}

/** @return array<string,mixed> */
function wpagent_formidable_field_schema( bool $allow_id ): array {
	$properties = array(
		'field_key' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 100, 'pattern' => '^[A-Za-z0-9_-]+$' ),
		'name' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 255 ),
		'description' => array( 'type' => 'string', 'maxLength' => 5000 ),
		'type' => array( 'type' => 'string', 'enum' => array( 'text', 'email', 'url', 'phone', 'number', 'textarea', 'checkbox', 'radio', 'select' ) ),
		'default_value' => array( 'anyOf' => array(
			array( 'type' => 'string', 'maxLength' => 10000 ), array( 'type' => 'number' ),
			array( 'type' => 'integer' ), array( 'type' => 'boolean' ),
			array( 'type' => 'array', 'maxItems' => 500, 'items' => array( 'type' => 'string', 'maxLength' => 10000 ) ),
		) ),
		'options' => array(
			'type' => 'array', 'maxItems' => 500,
			'items' => array( 'anyOf' => array(
				array( 'type' => 'string', 'maxLength' => 10000 ), array( 'type' => 'number' ),
				array( 'type' => 'integer' ), array( 'type' => 'boolean' ),
				array(
					'type' => 'object', 'additionalProperties' => false,
					'properties' => array(
						'label' => array( 'type' => 'string', 'maxLength' => 10000 ),
						'value' => array( 'type' => 'string', 'maxLength' => 10000 ),
					'image' => array( 'type' => 'string', 'maxLength' => 2048, 'pattern' => '^https?://' ),
					),
				),
			) ),
		),
		'field_order' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 10000 ),
		'required' => array( 'type' => 'boolean' ),
		'field_options' => wpagent_formidable_field_options_schema(),
	);
	if ( $allow_id ) {
		$properties = array( 'id' => array( 'type' => 'integer', 'minimum' => 1 ) ) + $properties;
	}
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => $properties,
		'anyOf'                => $allow_id
			? array( array( 'type' => 'object', 'required' => array( 'id' ) ), array( 'type' => 'object', 'required' => array( 'name', 'type' ) ) )
			: array( array( 'type' => 'object', 'required' => array( 'name', 'type' ) ) ),
	);
}

/** @param array<string,mixed> $field @return array<string,mixed> */
function wpagent_formidable_prepare_field_values( array $field, int $form_id, bool $creating ): array {
	$values = array();
	foreach ( array( 'name', 'description', 'type', 'default_value', 'options', 'field_order', 'required', 'field_options' ) as $key ) {
		if ( array_key_exists( $key, $field ) ) {
			$values[ $key ] = $field[ $key ];
		}
	}
	if ( array_key_exists( 'field_key', $field ) ) {
		$values['field_key'] = sanitize_title( (string) $field['field_key'] );
	}
	if ( isset( $values['name'] ) ) {
		$values['name'] = sanitize_text_field( (string) $values['name'] );
	}
	if ( isset( $values['description'] ) ) {
		$values['description'] = sanitize_textarea_field( (string) $values['description'] );
	}
	if ( isset( $values['type'] ) ) {
		$values['type'] = sanitize_key( (string) $values['type'] );
	}
	if ( isset( $values['required'] ) ) {
		$values['required'] = $values['required'] ? 1 : 0;
	}
	if ( isset( $values['field_order'] ) ) {
		$values['field_order'] = min( 10000, max( 0, (int) $values['field_order'] ) );
	}
	if ( isset( $values['field_options'] ) ) {
		$values['field_options'] = wpagent_formidable_safe_field_options( $values['field_options'] );
	}
	if ( array_key_exists( 'default_value', $values ) ) {
		$values['default_value'] = wpagent_formidable_plain_value( $values['default_value'] );
	}
	if ( isset( $values['options'] ) && is_array( $values['options'] ) ) {
		$values['options'] = wpagent_formidable_safe_options( $values['options'] );
	}
	if ( $creating ) {
		$values['form_id']       = $form_id;
		$values['description']   = (string) ( $values['description'] ?? '' );
		$values['default_value'] = $values['default_value'] ?? '';
		$values['options']       = is_array( $values['options'] ?? null ) ? $values['options'] : array();
		$values['field_options'] = is_array( $values['field_options'] ?? null ) ? $values['field_options'] : array();
	}
	return $values;
}

/** @param mixed $expected @param mixed $actual */
function wpagent_formidable_subset_matches( $expected, $actual ): bool {
	$expected = wpagent_formidable_json_value( $expected );
	$actual   = wpagent_formidable_json_value( $actual );
	if ( is_array( $expected ) ) {
		if ( ! is_array( $actual ) ) {
			return false;
		}
		foreach ( $expected as $key => $value ) {
			if ( ! array_key_exists( $key, $actual ) || ! wpagent_formidable_subset_matches( $value, $actual[ $key ] ) ) {
				return false;
			}
		}
		return true;
	}
	if ( is_bool( $expected ) || is_bool( $actual ) ) {
		return (bool) $actual === (bool) $expected;
	}
	return (string) $actual === (string) $expected;
}

/** @param mixed $expected @param mixed $actual */
function wpagent_formidable_exact_matches( $expected, $actual ): bool {
	$expected = wpagent_formidable_json_value( $expected );
	$actual   = wpagent_formidable_json_value( $actual );
	if ( is_array( $expected ) ) {
		if ( ! is_array( $actual ) || count( $expected ) !== count( $actual ) || array_is_list( $expected ) !== array_is_list( $actual ) ) {
			return false;
		}
		$expected_keys = array_map( 'strval', array_keys( $expected ) );
		$actual_keys   = array_map( 'strval', array_keys( $actual ) );
		sort( $expected_keys, SORT_STRING );
		sort( $actual_keys, SORT_STRING );
		if ( $expected_keys !== $actual_keys ) {
			return false;
		}
		foreach ( $expected as $key => $value ) {
			if ( ! array_key_exists( $key, $actual ) || ! wpagent_formidable_exact_matches( $value, $actual[ $key ] ) ) {
				return false;
			}
		}
		return true;
	}
	if ( is_bool( $expected ) || is_bool( $actual ) ) {
		return (bool) $actual === (bool) $expected;
	}
	return (string) $actual === (string) $expected;
}
