<?php

if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

class FrmProNestedFormsController {

	/**
	 * Display an embedded form on the current page
	 *
	 * @since 2.02.06
	 *
	 * @param array  $field
	 * @param string $field_name
	 * @param array  $errors
	 *
	 * @return void
	 */
	public static function display_front_end_embedded_form( $field, $field_name, $errors ) {
		$args = array(
			'errors' => $errors,
			'repeat' => 0,
		);
		self::display_front_end_nested_form( $field_name, $field, $args );
	}

	/**
	 * Display a repeating section on the current page
	 *
	 * @since 2.02.06
	 *
	 * @param array  $field
	 * @param string $field_name
	 * @param array  $errors
	 *
	 * @return void
	 */
	public static function display_front_end_repeating_section( $field, $field_name, $errors ) {
		$args = array(
			'errors' => $errors,
			'repeat' => 5,
		);
		self::display_front_end_nested_form( $field_name, $field, $args );
	}

	/**
	 * Display an embedded form/repeating section on the current page.
	 *
	 * @param string $field_name
	 * @param array  $field
	 * @param array  $args
	 *
	 * @return void
	 */
	public static function display_front_end_nested_form( $field_name, $field, $args = array() ) {
		if ( ! is_numeric( $field['form_select'] ) ) {
			return;
		}

		$defaults = array(
			'repeat' => 0,
			'errors' => array(),
		);

		$args    = wp_parse_args( $args, $defaults );
		$subform = FrmForm::getOne( $field['form_select'] );

		if ( ! $subform ) {
			return;
		}

		$subfields = FrmField::get_all_for_form( $field['form_select'] );

		self::insert_basic_hidden_field( $field_name . '[form]', $field['form_select'], '', compact( 'field' ) );

		if ( method_exists( 'FrmSubmitHelper', 'remove_submit_field_from_list' ) ) {
			FrmSubmitHelper::remove_submit_field_from_list( $subfields );
		}

		if ( ! $subfields ) {
			return;
		}

		$repeat_atts = array(
			'form'                    => $subform,
			'fields'                  => $subfields,
			'errors'                  => $args['errors'],
			'parent_field'            => $field,
			'repeat'                  => $args['repeat'],
			'field_name'              => $field_name,
			'passes_repeat_min_check' => self::passes_repeat_min_check( $field ),
			'repeat_min'              => array_key_exists( 'repeat_min', $field ) && is_numeric( $field['repeat_min'] ) ? $field['repeat_min'] : '',
			'is_repeat_limit_reached' => self::is_repeat_limit_reached_for_field( $field ),
		);

		if ( empty( $field['value'] ) ) {
			if ( array_key_exists( 'repeat_min', $field ) && is_numeric( $field['repeat_min'] ) ) {
				$start_rows = absint( $field['repeat_min'] );

				if ( ! $start_rows ) {
					// Always start with at least one row.
					$start_rows = 1;
				}
			} else {
				$start_rows = 1;
			}

			/**
			 * Row count must be zero if field value is empty.
			 *
			 * @since 2.0
			 *
			 * @param int   $start_rows
			 * @param array $field
			 */
			$start_rows = apply_filters( 'frm_repeat_start_rows', $start_rows, $field );

			for ( $i = 0, $j = $start_rows; $i < $j; $i++ ) {
				// Add an empty sub entry.
				$repeat_atts['row_count'] = $i;
				$repeat_atts['i']         = $i;
				self::display_single_iteration_of_nested_form( $field_name, $repeat_atts );
			}

			return;
		}

		$row_count          = 0;
		$processed_item_ids = array();

		foreach ( (array) $field['value'] as $k => $checked ) {
			$repeat_atts['i']     = $k;
			$repeat_atts['value'] = '';

			if ( ! isset( $field['value']['form'] ) ) {
				// This is not a posted value from moving between pages
				$checked = apply_filters( 'frm_hidden_value', $checked, $field );

				if ( ! $checked || ! is_numeric( $checked ) ) {
					continue;
				}

				$repeat_atts['i']        = 'i' . $checked;
				$repeat_atts['entry_id'] = $checked;
				$repeat_atts['value']    = $checked;
			} elseif ( $k === 'form' || $k === 'row_ids' ) {
				continue;
			} elseif ( str_starts_with( $k, 'i' ) ) {
				// Include the entry id when values are posted.
				$repeat_atts['entry_id'] = absint( str_replace( 'i', '', $k ) );
			} elseif ( self::new_entries_have_been_submitted() ) {
				$entry_id = self::get_new_inserted_repeater_item_id( $checked, $processed_item_ids );

				if ( $entry_id ) {
					$repeat_atts['i']        = 'i' . $entry_id;
					$repeat_atts['entry_id'] = $entry_id;
					$repeat_atts['value']    = $entry_id;
				}
			}

			if ( ! empty( $repeat_atts['entry_id'] ) ) {
				$processed_item_ids[] = (int) $repeat_atts['entry_id'];
			}

			// Keep track of row count.
			$repeat_atts['row_count'] = $row_count;
			++$row_count;

			// Show each existing sub entry.
			self::display_single_iteration_of_nested_form( $field_name, $repeat_atts );
			unset( $k, $checked );
		}

		unset( $subform, $subfields );
	}

	/**
	 * @since 6.8.4
	 *
	 * @param array $field
	 *
	 * @return bool
	 */
	private static function passes_repeat_min_check( $field ) {
		if ( ! array_key_exists( 'repeat_min', $field ) || ! is_numeric( $field['repeat_min'] ) ) {
			return true;
		}
		$row_count = self::get_row_count_for_field( $field );
		return $row_count > absint( $field['repeat_min'] );
	}

	/**
	 * @since 6.8.4
	 *
	 * @param array $field
	 *
	 * @return int
	 */
	private static function get_row_count_for_field( $field ) {
		if ( empty( $field['value'] ) ) {
			return 1;
		}

		if ( isset( $field['value']['row_ids'] ) ) {
			if ( is_array( $field['value']['row_ids'] ) ) {
				return count( $field['value']['row_ids'] );
			}

			return 1;
		}

		// When editing an entry, the value will be an array of child IDs on initial load
		return count( $field['value'] );
	}

	/**
	 * @return bool
	 */
	private static function new_entries_have_been_submitted() {
		global $frm_vars;
		return ! empty( $frm_vars['saved_entries'] );
	}

	/**
	 * Get the entry id for a repeater field that was just processed
	 * If data has just been posted, the new entry ids for repeaters are not passed, so we need to get them from the database
	 *
	 * @param array $values
	 * @param array $processed_item_ids
	 *
	 * @return int $entry_id 0 if no entry matches or if the entry has already been processed
	 */
	private static function get_new_inserted_repeater_item_id( $values, &$processed_item_ids ) {
		global $frm_vars;

		if ( empty( $frm_vars['saved_entries'] ) ) {
			return 0;
		}

		$values = array_filter(
			$values,
			function ( $value ) {
				return '' !== $value;
			}
		);

		if ( array() === $values ) {
			return 0;
		}

		// Name or Address field value is array and needs to be converted to string.
		self::convert_array_values_to_string( $values );

		$child_field_ids = array_keys( $values );
		$value           = array_values( $values );
		$where           = array(
			'item_id'    => $frm_vars['saved_entries'],
			'field_id'   => $child_field_ids,
			'meta_value' => $value,
		);
		$entry_id        = (int) FrmDb::get_var( 'frm_item_metas', $where, 'item_id' );

		if ( ! $entry_id || in_array( $entry_id, $processed_item_ids, true ) ) {
			return 0;
		}

		$processed_item_ids[] = $entry_id;
		return $entry_id;
	}

	/**
	 * Converts array values to string.
	 *
	 * @since 5.0.07
	 *
	 * @param array $arr The array.
	 *
	 * @return void
	 */
	private static function convert_array_values_to_string( &$arr ) {
		foreach ( $arr as &$value ) {
			if ( is_array( $value ) ) {
				$value = serialize( $value );
			}
		}
	}

	/**
	 * Check if repeat limit is reached for a section field
	 *
	 * @since 2.05
	 *
	 * @param array $field
	 *
	 * @return bool
	 */
	private static function is_repeat_limit_reached_for_field( $field ) {
		if ( empty( $field['repeat_limit'] ) ) {
			return false;
		}
		$row_count = self::get_row_count_for_field( $field );
		return self::is_repeat_limit_reached( $field['repeat_limit'], $row_count );
	}

	/**
	 * Add a repeating section row with ajax
	 */
	public static function ajax_add_repeat_row() {
		$field_id = FrmAppHelper::get_post_param( 'field_id', '', 'absint' );

		if ( ! $field_id ) {
			wp_die();
		}

		self::maybe_set_global_post();

		$row_count    = FrmAppHelper::get_post_param( 'numberOfSections', 0, 'absint' );
		$field        = FrmField::getOne( $field_id );
		$repeat_limit = absint( FrmField::get_option_in_object( $field, 'repeat_limit' ) );
		$repeat_min   = FrmField::get_option_in_object( $field, 'repeat_min' );

		if ( ! is_numeric( $repeat_min ) ) {
			$repeat_min = '';
		}

		$args = array(
			'i'            => FrmAppHelper::get_post_param( 'i', 0, 'absint' ),
			'parent_field' => $field->id,
			'form'         => $field->field_options['form_select'] ?? 0,
			'repeat'       => 1,
			'repeat_min'   => $repeat_min,
		);

		$field_name = 'item_meta[' . $args['parent_field'] . ']';

		if ( self::is_repeat_limit_reached( $repeat_limit, $row_count ) ) {
			echo json_encode( array() );
			wp_die();
		}

		$response = array(
			'passes_repeat_min_check' => is_numeric( $repeat_min ) ? $row_count >= $repeat_min : true,
			'repeat_min'              => $repeat_min,
			'is_repeat_limit_reached' => self::is_repeat_limit_reached( $repeat_limit, $row_count + 1 ),
		);

		$form = FrmForm::getOne( $field->form_id );

		if ( ! empty( $form->options['js_validate'] ) ) {
			FrmFormsController::add_js_validate_form_to_global_vars( $form );
		}

		FrmProEntriesController::maybe_include_exclude_fields( $form->id );

		ob_start();
		self::display_single_iteration_of_nested_form( $field_name, $args );
		$response['html'] = ob_get_clean();

		echo json_encode( $response );
		wp_die();
	}

	/**
	 * Check the form state for a global post ID.
	 * If one is defined, set the global $post object.
	 * This way shortcodes like [post_title] work when adding a repeater row.
	 *
	 * @since 6.8.4
	 *
	 * @return void
	 */
	private static function maybe_set_global_post() {
		global $post;

		$global_post_id = FrmProFormState::get_from_request( 'global_post', '' );

		if ( ! $global_post_id || ! is_numeric( $global_post_id ) ) {
			return;
		}

		$global_post = get_post( $global_post_id );

		if ( $global_post instanceof WP_Post ) {
			$post = $global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
		}
	}

	/**
	 * Check if the repeat limit is reached
	 *
	 * @since 2.05
	 *
	 * @param int $repeat_limit
	 * @param int $row_count
	 *
	 * @return bool
	 */
	private static function is_repeat_limit_reached( $repeat_limit, $row_count ) {
		return $repeat_limit && $row_count >= $repeat_limit;
	}

	/**
	 * Load JavaScript for hidden subfields
	 * Applies to repeating sections and embed form fields
	 * TODO: clean this up, maybe don't remove child fields from field array when they're on a different page
	 *
	 * @since 2.01.0
	 *
	 * @param array $field
	 *
	 * @return void
	 */
	public static function load_hidden_sub_field_javascript( $field ) {
		if ( ! self::is_hidden_nested_form_field( $field ) ) {
			return;
		}

		$child_fields = FrmField::get_all_for_form( $field['form_select'] );

		foreach ( $child_fields as $child_field_obj ) {
			$child_field = FrmProFieldsHelper::convert_field_object_to_flat_array( $child_field_obj );

			$child_field['original_type']  = $child_field['type'];
			$child_field['type']           = 'hidden';
			$child_field['parent_form_id'] = $field['form_id'];

			if ( ! isset( $child_field['value'] ) ) {
				$child_field['value'] = '';
			}

			if ( $field['original_type'] === 'form' ) {
				// This is needed when field script is loaded through hidden sub fields.
				$child_field['in_embed_form'] = $field['id'];
			}

			FrmProFieldsHelper::add_field_javascript( $child_field );
		}
	}

	/**
	 * Format the saved value in nested form fields when value is not posted
	 * This will only be used on initial form load when editing an entry
	 *
	 * @since 2.0
	 *
	 * @param array $field
	 */
	public static function format_saved_values_for_hidden_nested_forms( &$field ) {
		if ( ! is_numeric( $field['form_select'] ) ) {
			return;
		}

		$is_hidden_nested_form_field_with_saved_value = self::is_hidden_nested_form_field( $field ) &&
			! isset( $field['value']['form'] ) && ! empty( $field['value'] );

		if ( ! $is_hidden_nested_form_field_with_saved_value ) {
			return;
		}

		// Begin formatting field value
		$field['value'] = array(
			'form'    => $field['form_select'],
			'row_ids' => self::format_entry_ids_for_row_ids( $field['value'] ),
		);

		// Get child fields
		$child_fields = FrmField::get_all_for_form( $field['form_select'] );

		// Loop through children and entries to get values
		foreach ( $field['value']['row_ids'] as $row_id ) {
			$entry_id                  = str_replace( 'i', '', $row_id );
			$field['value'][ $row_id ] = array( 0 => '' );
			$entry                     = FrmEntry::getOne( $entry_id, true );

			foreach ( $child_fields as $child ) {
				$field['value'][ $row_id ][ $child->id ] = $entry->metas[ $child->id ] ?? '';

				if ( $child->type !== 'date' ) {
					continue;
				}

				$current_value                           = $field['value'][ $row_id ][ $child->id ];
				$field['value'][ $row_id ][ $child->id ] = FrmProAppHelper::maybe_convert_from_db_date( $current_value );
			}
		}
	}

	/**
	 * Check if a field is a hidden repeating section/embedded form
	 *
	 * @since 2.02.06
	 *
	 * @param array $field
	 *
	 * @return bool
	 */
	public static function is_hidden_nested_form_field( $field ) {
		$is_hidden_nested_form_field = false;

		if ( isset( $field['original_type'] ) && $field['type'] === 'hidden' ) {
			if ( $field['original_type'] === 'form' ) {
				$is_hidden_nested_form_field = true;
			} elseif ( $field['original_type'] === 'divider' && $field['repeat'] ) {
				$is_hidden_nested_form_field = true;
			}
		}

		return $is_hidden_nested_form_field;
	}

	/**
	 * Insert the fields and JavaScript for a hidden nested form
	 *
	 * @since 2.02.06
	 *
	 * @param array $field
	 * @param string $field_name
	 * @param array|string $field_value
	 */
	public static function insert_hidden_nested_form( $field, $field_name, $field_value ) {
		self::load_hidden_sub_field_javascript( $field );
		self::insert_hidden_nested_form_fields( $field, $field_name, $field_value );
	}

	/**
	 * Insert the fields in a hidden nested form
	 *
	 * @since 2.02.06
	 *
	 * @param array $field
	 * @param string $field_name
	 * @param array|string $value_array
	 */
	private static function insert_hidden_nested_form_fields( $field, $field_name, $value_array ) {
		if ( ! is_array( $value_array ) ) {
			self::insert_basic_hidden_field( $field_name, '', $field['html_id'], compact( 'field' ) );
			return;
		}

		foreach ( $value_array as $key => $value ) {
			if ( $key === 'form' ) {
				self::insert_basic_hidden_field( $field_name . '[' . $key . ']', $value, '', compact( 'field' ) );
			} elseif ( $key === 'row_ids' ) {
				self::insert_hidden_row_id_inputs( $field, $value );
			} else {
				self::insert_hidden_sub_field_inputs( $field, $field_name . '[' . $key . ']', $value, $key );
			}
		}
	}

	/**
	 * Insert the row_ids input for a nested form
	 *
	 * @since 2.02.06
	 *
	 * @param array        $field
	 * @param array|string $value
	 *
	 * @return void
	 */
	private static function insert_hidden_row_id_inputs( $field, $value ) {
		if ( ! is_array( $value ) ) {
			$value = array( 0 );
		}

		$name = 'item_meta[' . $field['id'] . '][row_ids][]';

		foreach ( $value as $row_id ) {
			self::insert_basic_hidden_field( $name, $row_id, '', compact( 'field' ) );
		}
	}

	/**
	 * Insert the sub fields in a hidden nested form
	 *
	 * @since 2.02.06
	 *
	 * @param array        $field
	 * @param string       $field_name
	 * @param array|string $value
	 * @param string       $value_key
	 *
	 * @return void
	 */
	private static function insert_hidden_sub_field_inputs( $field, $field_name, $value, $value_key ) {
		if ( is_array( $value ) ) {
			foreach ( $value as $k => $checked2 ) {
				$checked2 = apply_filters( 'frm_hidden_value', $checked2, $field );
				self::insert_hidden_sub_field_inputs( $field, $field_name . '[' . $k . ']', $checked2, $k );
			}
			unset( $k, $checked2 );
		} else {
			$html_id = self::get_html_id_for_hidden_sub_fields( $field_name, $value_key, $field['html_id'] );
			self::insert_basic_hidden_field( $field_name, $value, $html_id, compact( 'field' ) );
		}
	}

	/**
	 * Insert a basic hidden field.
	 *
	 * @since 2.02.06
	 *
	 * @param string $name
	 * @param string $value
	 * @param string $id
	 * @param array  $args
	 *
	 * @return void
	 */
	private static function insert_basic_hidden_field( $name, $value, $id, $args = array() ) {
		$class = str_contains( $name, '[form]' ) ? 'frm_dnc' : '';

		if ( $id ) {
			$html        = '';
			$subfield_id = self::get_sub_field_id( $name );

			if ( isset( $args['field'] ) && $args['field']['id'] == $subfield_id ) {
				$field = $args['field'];
			} else {
				$field = $subfield_id ? FrmField::getOne( $subfield_id ) : null;
			}

			if ( $field ) {
				$field                  = (array) $field;
				$field['original_type'] = $field['original_type'] ?? $field['type'];
				$field['type']          = 'hidden';
				$field['value']         = $value;
				FrmProFieldsController::add_currency_field_attributes( $field, $html, $args['field'] );

				if ( isset( $args['field'] ) && isset( $args['field']['parent_form_id'] ) ) {
					// Pass the parent ID on as needed.
					$field['form_id'] = $args['field']['parent_form_id'];
				}
			}

			?><input type="hidden" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $id ); ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>/>
			<?php
			if ( isset( $field ) && isset( $field['original_type'] ) && ( $field['original_type'] === 'product' || $field['original_type'] === 'total' ) ) {
				FrmProCurrencyHelper::add_currency_to_global( $field['form_id'] );
			}
		} else {
			?><input type="hidden" name="<?php echo esc_attr( $name ); ?>" value="<?php echo esc_attr( $value ); ?>" class="<?php echo esc_attr( $class ); ?>" />
			<?php
		}
	}

	/**
	 * @param string $field_name
	 *
	 * @return int|string
	 */
	private static function get_sub_field_id( $field_name ) {
		$matches = array();
		// .+ is safer than d+ because field keys might be used at times.
		preg_match( '/item_meta(\[.+?\]){3}/i', $field_name, $matches, PREG_OFFSET_CAPTURE );

		if ( ! count( $matches ) ) {
			return 0;
		}

		$name  = $matches[0][0];
		$parts = explode( '][', $name . '[' );
		return $parts[ count( $parts ) - 2 ];
	}

	/**
	 * Make sure fields in a hidden repeating section get the correct HTML ids
	 * Their HTML ID should be the same when hidden or visible to work with calculations and logic
	 *
	 * @since 2.02.06
	 *
	 * @param string $field_name
	 * @param string $opt_key
	 * @param string $html_id
	 *
	 * @return string
	 */
	private static function get_html_id_for_hidden_sub_fields( $field_name, $opt_key, $html_id ) {
		$parts = explode( '][', $field_name . '[' );

		if ( count( $parts ) <= 2 ) {
			return $html_id;
		}

		if ( $parts[2] === 'other' ) {
			$html_id = self::get_html_id_for_hidden_other_fields( $parts, $opt_key, $html_id );
		} else {
			$field_id = absint( $parts[2] );

			if ( $field_id === 0 ) {
				$html_id = '';
			} else {
				$field_key = FrmField::get_key_by_id( $field_id );

				if ( $field_key ) {
					$html_id = 'field_' . $field_key . '-' . $parts[1];

					// Allow for a multi-dimensional array for the ids
					if ( isset( $parts[3] ) && $parts[3] !== '' ) {
						$html_id .= '-' . $parts[3];
					}
				}
			}
		}

		return $html_id;
	}

	/**
	 * Get the HTML ID for hidden other fields inside of repeating sections when value is posted
	 *
	 * @since 2.0.8
	 *
	 * @param array $parts (array of the field name)
	 * @param bool|string $opt_key
	 * @param string $html_id
	 *
	 * @return string
	 */
	private static function get_html_id_for_hidden_other_fields( $parts, $opt_key, $html_id ) {
		$field_id  = absint( $parts[3] );
		$field_key = FrmField::get_key_by_id( $field_id );

		if ( ! $field_key ) {
			return $html_id;
		}

		$html_id = 'field_' . $field_key . '-' . $parts[1];

		// If checkbox field or multi-select dropdown
		if ( $opt_key && FrmFieldsHelper::is_other_opt( $opt_key ) ) {
			$html_id .= '-' . $opt_key . '-otext';
		} else {
			$html_id .= '-otext';
		}

		return $html_id;
	}

	/**
	 * Convert entry IDs to row IDs
	 *
	 * @since 2.02.06
	 *
	 * @param array|string $entry_ids
	 *
	 * @return array
	 */
	private static function format_entry_ids_for_row_ids( $entry_ids ) {
		$row_ids = array();

		foreach ( (array) $entry_ids as $entry_id ) {
			if ( $entry_id ) {
				$row_ids[] = 'i' . $entry_id;
			}
		}

		return $row_ids;
	}

	/**
	 * Display a single iteration of a nested form
	 *
	 * @since 2.02.06
	 *
	 * @param string $field_name
	 * @param array  $args
	 *
	 * @return void
	 */
	private static function display_single_iteration_of_nested_form( $field_name, $args = array() ) {
		$defaults = array(
			'i'                       => 0,
			'entry_id'                => false,
			'form'                    => false,
			'fields'                  => array(),
			'errors'                  => array(),
			'parent_field'            => 0,
			'repeat'                  => 0,
			'row_count'               => false,
			'value'                   => '',
			'field_name'              => '',
			'passes_repeat_min_check' => true,
			'repeat_min'              => '',
			'is_repeat_limit_reached' => false,
		);
		$args     = wp_parse_args( $args, $defaults );

		if ( empty( $args['parent_field'] ) ) {
			return;
		}

		if ( is_numeric( $args['parent_field'] ) ) {
			$args['parent_field']           = (array) FrmField::getOne( $args['parent_field'] );
			$args['parent_field']['format'] = $args['parent_field']['field_options']['format'] ?? '';
		}

		FrmForm::maybe_get_form( $args['form'] );

		if ( empty( $args['fields'] ) ) {
			$args['fields'] = FrmField::get_all_for_form( $args['form']->id );
		}

		$values = array();

		if ( $args['fields'] ) {
			$pass_args = array(
				'parent_form_id'  => $args['parent_field']['form_id'],
				'parent_field_id' => $args['parent_field']['id'],
				'key_pointer'     => $args['i'],
				'repeating'       => true,
				'fields'          => $args['fields'],
				'in_embed_form'   => $args['parent_field']['type'] === 'form' ? $args['parent_field']['id'] : '0',
			);

			// Find the connection between here and where logic is packaged

			if ( empty( $args['entry_id'] ) ) {
				$just_created_entry = FrmFormsController::just_created_entry( $args['parent_field']['form_id'] );
				$values             = FrmEntriesHelper::setup_new_vars( $args['fields'], $args['form'], $just_created_entry, $pass_args );
			} else {
				$entry = FrmEntry::getOne( $args['entry_id'], true );

				if ( ! $entry || $entry->form_id != $args['form']->id ) {
					return;
				}

				$values = FrmProEntriesController::setup_entry_values_for_editing( $entry, $pass_args );
			}
		}

		$format = $args['parent_field']['format'] ?? '';
		$end    = self::get_end_repeat_field( $args );
		$count  = 0;

		foreach ( $values['fields'] as $subfield ) {
			if ( 'end_divider' === $subfield['type'] ) {
				$end = $subfield;
			} elseif ( ! in_array( $subfield['type'], array( 'hidden', 'user_id' ), true ) ) {
				if ( ! empty( $subfield['conf_field'] ) ) {
					$count = $count + 2;
				} else {
					++$count;
				}
			}
			unset( $subfield );
		}

		if ( $args['repeat'] ) {
			++$count;
		}

		$field_class     = self::grid_field_class( $count, $format );
		$section_classes = self::repeat_container_classes( $format, $args );

		echo '<div';
		FrmAppHelper::array_to_html_params(
			array(
				'id'                => 'frm_section_' . $args['parent_field']['id'] . '-' . $args['i'],
				'class'             => $section_classes,
				'data-column-count' => $count,
				'data-link-format'  => ! empty( $end['format'] ) ? $end['format'] : 'icon',
			),
			true
		);
		echo '>' . "\n";

		self::add_hidden_repeat_row_id( $args );
		self::add_default_item_meta_field( $args );

		/**
		 * Allows changing subfields in the repeater field.
		 *
		 * @since 5.0
		 *
		 * @param array $subfields Subfields.
		 * @param array $args      Custom arguments.
		 */
		$values['fields'] = apply_filters( 'frm_subfields_in_repeater', $values['fields'], $args );

		$label_pos = 'top';
		$field_num = 1;

		foreach ( $values['fields'] as $subfield ) {
			$subfield_name    = $field_name . '[' . $args['i'] . '][' . $subfield['id'] . ']';
			$subfield_plus_id = '-' . $args['i'];
			$subfield_id      = $subfield['id'] . '-' . $args['parent_field']['id'] . $subfield_plus_id;

			if ( $args['parent_field'] && ! empty( $args['parent_field']['value'] ) && isset( $args['parent_field']['value']['form'] ) && isset( $args['parent_field']['value'][ $args['i'] ] ) && isset( $args['parent_field']['value'][ $args['i'] ][ $subfield['id'] ] ) ) {
				// This is a posted value from moving between pages, so set the POSTed value
				$subfield['value'] = $args['parent_field']['value'][ $args['i'] ][ $subfield['id'] ];
			}

			if ( $field_class ) {
				if ( 1 === $field_num ) {
					$subfield['classes'] .= ' frm_first';
				}
				self::add_class_to_field( $field_class, 'field', $subfield['classes'] );
			}

			++$field_num;

			if ( 'top' === $label_pos && in_array( $subfield['label'], array( 'top', 'hidden', '', 'inside' ) ) ) {
				// Add placeholder label if repeating
				$label_pos = 'hidden';
			}

			// Track whether field is in an embedded form
			if ( 'form' === $args['parent_field']['type'] ) {
				// TODO: Check if this is needed
				$subfield['in_embed_form'] = $args['parent_field']['id'];
			}

			$field_args = array(
				'field_name'     => $subfield_name,
				'field_id'       => $subfield_id,
				'field_plus_id'  => $subfield_plus_id,
				'section_id'     => $args['parent_field']['id'],
				'errors'         => $args['errors'],
				'form'           => $args['form'],
				'parent_form_id' => $args['parent_field']['form_id'],
			);
			$field_obj  = FrmFieldFactory::get_field_type( $subfield['type'], $subfield );
			$field_obj->show_field( $field_args );

			unset( $subfield_name, $subfield_id );
		}

		if ( ! $args['repeat'] ) {
			// Close frm_repeat div
			echo '</div>' . "\n";

			return;
		}

		$args['format']      = $format;
		$args['label_pos']   = $label_pos;
		$args['field_class'] = $field_class;
		echo self::get_repeat_buttons( $args, $end ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		// Close frm_repeat div
		echo '</div>' . "\n";
	}

	/**
	 * @since 3.0
	 *
	 * @param int    $count
	 * @param string $format
	 *
	 * @return array|string
	 */
	private static function grid_field_class( $count, $format ) {
		if ( ! $format ) {
			return '';
		}

		if ( 2 == $count ) {
			$class = array( 10, 2 );
		} elseif ( $count < 13 ) {
			$field_width  = floor( 12 / $count );
			$submit_width = 12 - ( $field_width * ( $count - 1 ) );
			$class        = array( $field_width, $submit_width );
		} else {
			$class = '';
		}

		return $class;
	}

	/**
	 * @param array|string $add_class
	 * @param string       $type
	 * @param string       $classes
	 *
	 * @return void
	 */
	private static function add_class_to_field( $add_class, $type, &$classes ) {
		if ( is_array( $add_class ) ) {
			$position = 'button' === $type ? 1 : 0;
			$classes .= ' frm' . $add_class[ $position ];
		} elseif ( $add_class ) {
			$classes .= ' frm' . $add_class;
		}
	}

	/**
	 * @since 3.0
	 *
	 * @param string $format
	 * @param array  $args
	 *
	 * @return string
	 */
	private static function repeat_container_classes( $format, $args ) {
		$section_classes = array(
			'frm_repeat_' . ( $format ? $format : 'sec' ),
			'frm_repeat_' . $args['parent_field']['id'] . ( $args['row_count'] === 0 ? ' frm_first_repeat' : '' ),
			'frm_grid_container',
		);
		return implode( ' ', $section_classes );
	}

	/**
	 * Adds the row_ids to a nested form on the current page
	 *
	 * @since 2.02.06
	 *
	 * @param array $args
	 *
	 * @return void
	 */
	private static function add_hidden_repeat_row_id( $args ) {
		echo '<input type="hidden" name="item_meta[' . esc_attr( $args['parent_field']['id'] ) . '][row_ids][]" value="' . esc_attr( $args['i'] ) . '" />';
	}

	/**
	 * Add item meta to each row in repeating section or embedded form so the entry is always validated
	 *
	 * @since 2.0.08
	 *
	 * @param array $args
	 *
	 * @return void
	 */
	private static function add_default_item_meta_field( $args ) {
		echo '<input type="hidden" name="item_meta[' . esc_attr( $args['parent_field']['id'] ) . '][' . esc_attr( $args['i'] ) . '][0]" value="" />';
	}

	/**
	 * Get the HTML for repeat buttons.
	 *
	 * @param array $args
	 * @param bool  $end
	 *
	 * @return mixed|void
	 */
	private static function get_repeat_buttons( $args, $end = false ) {
		$args['end_format'] = 'icon';

		if ( ! $end ) {
			$end = self::get_end_repeat_field( $args );
		}

		if ( $end ) {
			$args['add_label']    = $end['add_label'];
			$args['remove_label'] = $end['remove_label'];

			if ( ! empty( $end['format'] ) ) {
				$args['end_format'] = $end['format'];
			}
		}

		$triggers = self::repeat_button_html( $args, $end );

		return apply_filters( 'frm_repeat_triggers', $triggers, $end, $args['parent_field'], $args['field_class'] );
	}

	/**
	 * Get the end divider field for a repeating section
	 *
	 * @param array $args
	 *
	 * @return array|false
	 */
	private static function get_end_repeat_field( $args ) {
		$query     = array(
			'fi.form_id'    => $args['parent_field']['form_id'],
			'type'          => 'end_divider',
			'field_order >' => $args['parent_field']['field_order'] + 1,
		);
		$end_field = FrmField::getAll( $query, 'field_order', 1 );

		if ( ! $end_field ) {
			return false;
		}

		$field_array = FrmProFieldsHelper::initialize_array_field( $end_field );

		foreach ( array( 'format', 'add_label', 'remove_label', 'classes' ) as $o ) {
			if ( isset( $end_field->field_options[ $o ] ) ) {
				$field_array[ $o ] = $end_field->field_options[ $o ];
			}
		}

		FrmFieldsHelper::prepare_new_front_field( $field_array, $end_field );

		return $field_array;
	}

	/**
	 * Get the HTML for the repeat buttons
	 *
	 * @param array $args
	 * @param array $end
	 *
	 * @return string
	 */
	private static function repeat_button_html( $args, $end ) {
		$defaults = array(
			'add_icon'                => '',
			'remove_icon'             => '',
			'add_label'               => __( 'Add', 'formidable' ),
			'remove_label'            => __( 'Remove', 'formidable' ),
			'passes_repeat_min_check' => true,
			'repeat_min'              => '',
			'is_repeat_limit_reached' => false,
			'add_classes'             => '',
			'remove_classes'          => '',
		);

		$args = wp_parse_args( $args, $defaults );

		if ( ! isset( $args['end_format'] ) && isset( $args['format'] ) ) {
			$args['end_format'] = $args['format'];
		}

		if ( 'text' !== $args['end_format'] ) {
			$form_style     = FrmStylesController::get_form_style( $args['form'] );
			$style_settings = FrmStylesHelper::get_settings_for_output( $form_style );
			$repeat_icon    = $style_settings['repeat_icon'] ?? 1;
			$svg_args       = array(
				'echo'   => false,
				'width'  => '1em',
				'height' => '1em',
			);

			$args['remove_icon'] = FrmProAppHelper::get_svg_icon( FrmStylesHelper::icon_key_to_class( $repeat_icon, '-', '' ), 'frmsvg frm-svg-icon', $svg_args ) . ' ';
			$args['add_icon']    = FrmProAppHelper::get_svg_icon( FrmStylesHelper::icon_key_to_class( $repeat_icon, '+', '' ), 'frmsvg frm-svg-icon', $svg_args ) . ' ';
		}

		if ( 'icon' === $args['end_format'] ) {
			$args['add_label']    = '';
			$args['remove_label'] = '';
		} else {
			$args['add_classes']    = ' frm_button';
			$args['remove_classes'] = ' frm_button';
		}

		if ( ! $args['passes_repeat_min_check'] ) {
			$args['remove_classes'] .= ' frm_hide_remove_button';
		}

		if ( $args['is_repeat_limit_reached'] ) {
			$args['add_classes'] .= ' frm_hide_add_button';
		}

		$classes = 'frm_form_field frm_' . $args['label_pos'] . '_container frm_repeat_buttons';

		self::add_class_to_field( $args['field_class'], 'button', $classes );

		// Get classes for end divider
		$classes .= $end && isset( $end['classes'] ) ? ' ' . $end['classes'] : '';

		$repeat_min = '';

		if ( $args['repeat_min'] && is_numeric( $args['repeat_min'] ) ) {
			$repeat_min = ' data-repeat-min="' . absint( $args['repeat_min'] ) . '" ';
		}

		$triggers = '<div class="' . esc_attr( $classes ) . '"' . $repeat_min . '>';

		if ( 'hidden' === $args['label_pos'] && ! empty( $args['format'] ) ) {
			$triggers .= '<label class="frm_primary_label">&nbsp;</label>';
		}

		$triggers .= '<a href="#" class="frm_add_form_row' . esc_attr( $args['add_classes'] ) . '" data-parent="' . esc_attr( $args['parent_field']['id'] ) . '" aria-label="' . esc_attr( $defaults['add_label'] ) . '">' . $args['add_icon'] . $args['add_label'] . '</a>' . "\n";
		$triggers .= '<a href="#" class="frm_remove_form_row' . esc_attr( $args['remove_classes'] ) . '" data-key="' . esc_attr( $args['i'] ) . '" data-parent="' . esc_attr( $args['parent_field']['id'] ) . '" aria-label="' . esc_attr( $defaults['remove_label'] ) . '">' . $args['remove_icon'] . $args['remove_label'] . '</a> ';

		return $triggers . '</div>';
	}

	/**
	 * Gets IDs of the forms that contain the embedded form.
	 *
	 * @since 6.32
	 *
	 * @param int $embedded_form_id The ID of the embedded form.
	 *
	 * @return array
	 */
	public static function get_forms_contain_embedded_form( $embedded_form_id ) {
		$like_value = sprintf(
			's:11:"form_select";s:%1$d:"%2$d";',
			strlen( (string) $embedded_form_id ),
			$embedded_form_id
		);

		return FrmDb::get_col(
			'frm_fields',
			array(
				'type'               => 'form',
				'field_options LIKE' => $like_value,
			),
			'form_id'
		);
	}
}
