<?php

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

class FrmProField {

	/**
	 * @param array $field_data
	 *
	 * @return array
	 */
	public static function create( $field_data ) {
		if ( $field_data['field_options']['label'] !== 'none' ) {
			$field_data['field_options']['label'] = '';
		}

		self::switch_in_section_field_option( $field_data );

		switch ( $field_data['type'] ) {
			case 'select':
				$width                               = FrmStylesController::get_style_val( 'auto_width', $field_data['form_id'] );
				$field_data['field_options']['size'] = $width;
				break;
			case 'divider':
				if ( ! empty( $field_data['field_options']['repeat'] ) ) {
					// Create the repeatable form.
					$field_data['field_options']['form_select'] = self::create_repeat_form(
						0,
						array(
							'parent_form_id' => $field_data['form_id'],
							'field_name'     => $field_data['name'],
						)
					);
				}
				break;
			case 'file':
				$field_data['field_options']['restrict'] = 1;

				if ( ! $field_data['field_options']['ftypes'] ) {
					$field_data['field_options']['ftypes'] = array(
						'jpg|jpeg|jpe' => 'image/jpeg',
						'png'          => 'image/png',
						'gif'          => 'image/gif',
					);
				}
				break;
		}

		return $field_data;
	}

	/**
	 * Change the default in_section value to the ID of the section where a new field was dragged and dropped
	 *
	 * @since 2.0.24
	 *
	 * @param array $field_data
	 *
	 * @return void
	 */
	private static function switch_in_section_field_option( &$field_data ) {
		if ( in_array( $field_data['type'], array( 'divider', 'end_divider', 'form' ), true ) ) {
			return;
		}

		if ( self::maybe_use_repeater_form_id( $field_data ) ) {
			// Do not override the in_section value if it is already set
			// This way it can be passed in field_options when the field is created.
			return;
		}

		$ajax_action = FrmAppHelper::get_post_param( 'action', '', 'sanitize_title' );

		if ( 'frm_insert_field' !== $ajax_action ) {
			return;
		}

		$section_id                                = FrmAppHelper::get_post_param( 'section_id', 0, 'absint' );
		$field_data['field_options']['in_section'] = $section_id;
	}

	/**
	 * When insertFormField is called, it always passes the parent form ID.
	 * So map it to the repeater's form ID when applicable.
	 *
	 * @since 6.24
	 *
	 * @param array $field_data
	 *
	 * @return bool True if the section logic has already been handled. When this is true, we exit switch_in_section_field_option early.
	 */
	private static function maybe_use_repeater_form_id( &$field_data ) {
		if ( empty( $field_data['field_options']['in_section'] ) ) {
			return false;
		}

		$section_field = FrmField::getOne( $field_data['field_options']['in_section'] );

		if ( $section_field && ! empty( $section_field->field_options['repeat'] ) ) {
			$field_data['form_id'] = $section_field->field_options['form_select'];
		}

		return true;
	}

	/**
	 * @since 3.0
	 *
	 * @param array $settings
	 *
	 * @return array
	 */
	public static function skip_update_field_setting( $settings ) {
		unset( $settings['post_field'], $settings['custom_field'] );
		unset( $settings['taxonomy'], $settings['exclude_cat'] );
		return $settings;
	}

	/**
	 * @param array    $field_options
	 * @param stdClass $field
	 * @param array    $values
	 *
	 * @return array
	 */
	public static function update( $field_options, $field, $values ) {
		foreach ( $field_options['hide_field'] as $i => $f ) {
			if ( ! $f ) {
				unset( $field_options['hide_field'][ $i ], $field_options['hide_field_cond'][ $i ] );

				if ( isset( $field_options['hide_opt'] ) && is_array( $field_options['hide_opt'] ) ) {
					unset( $field_options['hide_opt'][ $i ] );
				}
			}
			unset( $i, $f );
		}

		if ( $field->type === 'hidden' && ! empty( $field_options['required'] ) ) {
			$field_options['required'] = false;
		} elseif ( $field->type === 'file' ) {
			self::format_mime_types( $field_options, $field->id );
		}

		$field_options['custom_currency'] = 0; // This setting no longer exists.

		if ( isset( $field_options['custom_decimals'] ) ) {
			$field_options['custom_decimals'] = absint( $field_options['custom_decimals'] );
		}

		// Ensure proper handling when the format dropdown is "None".
		if ( isset( $field_options['format'] ) && '' === $field_options['format'] && isset( $field_options['calc_dec'] ) && is_numeric( $field_options['calc_dec'] ) ) {
			$field_options['calc_dec'] = '';
		}

		$field_options = self::sanitize_custom_thousand_separator( $field_options );
		$field_options = self::update_show_slider_range_value( $field->type, $field_options );

		return self::reset_conditional_logic_settings( $field->id, $field_options );
	}

	/**
	 * @since 6.20
	 *
	 * @param string $field_type
	 * @param array  $field_options
	 *
	 * @return array
	 */
	private static function update_show_slider_range_value( $field_type, $field_options ) {
		if ( 'range' !== $field_type ) {
			return $field_options;
		}
		$field_options['show_slider_range'] = absint( $field_options['show_slider_range'] );
		return $field_options;
	}

	/**
	 * Return true if the conditional logic settings should be reset.
	 *
	 * @since 6.32
	 *
	 * @param array $field_options
	 *
	 * @return bool
	 */
	private static function should_reset_conditional_logic_settings( $field_options ) {
		if ( empty( $field_options['hide_field'] ) ) {
			return true;
		}

		return isset( $field_options['enable_conditional_logic'] ) && '0' === $field_options['enable_conditional_logic'];
	}

	/**
	 * If the conditional logic is disabled, reset its settings.
	 *
	 * @since 6.24
	 *
	 * @param int   $field_id The field ID.
	 * @param array $field_options The field options.
	 *
	 * @return array
	 */
	private static function reset_conditional_logic_settings( $field_id, $field_options ) {
		if ( ! self::should_reset_conditional_logic_settings( $field_options ) ) {
			return $field_options;
		}

		$defaults = array(
			'enable_conditional_logic' => '0',
			'show_hide'                => 'show',
			'any_all'                  => 'any',
			'hide_field'               => array(),
			'hide_field_cond'          => array( '==' ),
			'hide_opt'                 => array(),
		);

		foreach ( $defaults as $key => $value ) {
			$field_options[ $key ] = $value;
			// Update POST data to reset the conditional logic settings after the Form Builder is updated and the page reloads.
			$_POST['field_options'][ $key . '_' . $field_id ] = $value;
		}

		return $field_options;
	}

	/**
	 * @param array      $options
	 * @param int|string $field_id
	 *
	 * @return void
	 */
	private static function format_mime_types( &$options, $field_id ) {
		$file_options = $options['ftypes'] ?? array();

		if ( ! $file_options ) {
			return;
		}

		$mime_array = array();

		foreach ( $file_options as $file_option ) {
			$values                   = explode( '|||', $file_option );
			$mime_array[ $values[0] ] = $values[1];
		}

		$options['ftypes']                               = $mime_array;
		$_POST['field_options'][ 'ftypes_' . $field_id ] = $mime_array;
	}

	/**
	 * Sanitize the custom thousand separator as sanitizing has been disabled for this option.
	 * This is a special edge case because we do not want to trim the thousand separator.
	 *
	 * @since 5.5.6
	 *
	 * @param array $field_options
	 *
	 * @return array
	 */
	private static function sanitize_custom_thousand_separator( $field_options ) {
		if ( ! empty( $field_options['custom_thousand_separator'] ) ) {
			$field_options['custom_thousand_separator'] = strip_tags( $field_options['custom_thousand_separator'] );
		}
		return $field_options;
	}

	/**
	 * Switches quantity field's product fields to the new field IDs.
	 *
	 * @since 6.19
	 *
	 * @param array $values
	 *
	 * @return void
	 */
	private static function switch_quantity_product_field_ids( &$values ) {
		if ( $values['type'] !== 'quantity' || empty( $values['field_options']['product_field'] ) ) {
			return;
		}
		global $frm_duplicate_ids;

		foreach ( $values['field_options']['product_field'] as $index => $field_id ) {
			if ( ! empty( $frm_duplicate_ids[ $field_id ] ) ) {
				$values['field_options']['product_field'][ $index ] = (string) $frm_duplicate_ids[ $field_id ];
			}
		}
	}

	/**
	 * @param array $values
	 * @param array $atts {
	 *
	 *     @type bool $after True on the second run.
	 * }
	 *
	 * @return array
	 */
	public static function duplicate( $values, $atts = array() ) {
		global $frm_duplicate_ids;

		$is_second_run = $atts['after'] ?? false;

		if ( ! $frm_duplicate_ids || empty( $values['field_options'] ) ) {
			if ( ! $is_second_run ) {
				self::mark_field_key_as_unprocessed( $values['field_key'] );
			}

			return $values;
		}

		// Switch out fields from calculation or default values
		$switch_string = array( 'default_value', 'calc' );

		foreach ( $switch_string as $opt ) {
			if ( empty( $values['field_options'][ $opt ] ) && empty( $values[ $opt ] ) ) {
				continue;
			}

			$this_val = $values[ $opt ] ?? $values['field_options'][ $opt ];

			if ( is_array( $this_val ) ) {
				continue;
			}

			$ids = FrmProFieldsHelper::filter_keys_for_regex( $this_val, array_keys( $frm_duplicate_ids ) );

			if ( ! $ids ) {
				continue;
			}

			$ids = implode( '|', $ids );

			preg_match_all( '/\[(' . $ids . ')\]/s', $this_val, $matches, PREG_PATTERN_ORDER );
			unset( $ids );

			if ( ! isset( $matches[1] ) ) {
				unset( $matches );
				continue;
			}

			foreach ( $matches[1] as $val ) {
				if ( $is_second_run && in_array( $val, $frm_duplicate_ids ) ) {
					// The field id may have already been replaced.
					continue;
				}

				$this_val = str_replace( '[' . $val . ']', '[' . $frm_duplicate_ids[ $val ] . ']', $this_val );

				if ( isset( $values[ $opt ] ) ) {
					$values[ $opt ] = $this_val;
				} else {
					$values['field_options'][ $opt ] = $this_val;
				}
				unset( $val );
			}

			unset( $this_val, $matches );
		}

		// Switch out field ids in conditional logic
		if ( ! empty( $values['field_options']['hide_field'] ) ) {
			foreach ( array( 'hide_field_cond', 'hide_opt', 'hide_field' ) as $logic ) {
				if ( isset( $values['field_options'][ $logic ] ) ) {
					FrmAppHelper::unserialize_or_decode( $values['field_options'][ $logic ] );
				} else {
					$values['field_options'][ $logic ] = array();
				}
			}

			$processed = false;

			foreach ( $values['field_options']['hide_field'] as $k => $f ) {
				if ( $is_second_run && in_array( $f, $frm_duplicate_ids ) ) {
					// The field id may have already been replaced.
					continue;
				}

				if ( isset( $frm_duplicate_ids[ $f ] ) ) {
					$processed                                   = true;
					$values['field_options']['hide_field'][ $k ] = $frm_duplicate_ids[ $f ];
				}
				unset( $k, $f );
			}

			if ( ! $processed && ! $is_second_run ) {
				self::mark_field_key_as_unprocessed( $values['field_key'] );
			}

			unset( $processed );
		}

		self::switch_out_form_select( $frm_duplicate_ids, $values );
		self::switch_id_for_section_tracking_field_option( $frm_duplicate_ids, $values );
		self::switch_ids_for_lookup_settings( $frm_duplicate_ids, $values );
		self::switch_quantity_product_field_ids( $values );

		return $values;
	}

	/**
	 * Track the field keys that have not yet had replaced their conditional logic to replace after duplicate as they rely on a different field order.
	 *
	 * @param string $field_key
	 */
	private static function mark_field_key_as_unprocessed( $field_key ) {
		global $frm_unprocessed_duplicate_field_keys;

		if ( ! is_array( $frm_unprocessed_duplicate_field_keys ) ) {
			$frm_unprocessed_duplicate_field_keys = array();
		}

		$frm_unprocessed_duplicate_field_keys[] = $field_key;
	}

	/**
	 * Switch out field ids if selected in a Dynamic Field
	 *
	 * @since 2.0.25
	 *
	 * @param array $frm_duplicate_ids
	 * @param array $values
	 */
	private static function switch_out_form_select( $frm_duplicate_ids, &$values ) {
		if ( 'data' === $values['type'] && FrmField::is_option_true_in_array( $values['field_options'], 'form_select' ) ) {
			self::maybe_switch_field_id_in_setting( $frm_duplicate_ids, 'form_select', $values['field_options'] );
		}
	}

	/**
	 * Switch the in_section ID when a field is duplicated
	 *
	 * @since 2.0.25
	 *
	 * @param array $frm_duplicate_ids
	 * @param array $values
	 */
	private static function switch_id_for_section_tracking_field_option( $frm_duplicate_ids, &$values ) {
		if ( isset( $values['field_options']['in_section'] ) ) {
			self::maybe_switch_field_id_in_setting( $frm_duplicate_ids, 'in_section', $values['field_options'] );
		} else {
			$values['field_options']['in_section'] = 0;
		}
	}

	/**
	 * Switch the get_values_form, get_values_field, and watch_lookup IDs when a field is imported
	 *
	 * @since 2.01.0
	 *
	 * @param array $frm_duplicate_ids
	 * @param array $values
	 */
	private static function switch_ids_for_lookup_settings( $frm_duplicate_ids, &$values ) {
		if ( ! FrmField::is_option_true_in_array( $values['field_options'], 'get_values_field' ) ) {
			return;
		}

		self::maybe_switch_field_id_in_setting( $frm_duplicate_ids, 'get_values_field', $values['field_options'] );
		self::switch_watch_lookup_ids( $frm_duplicate_ids, $values );
	}

	/**
	 * Switch the watch_lookup ids when a lookup field is duplicated.
	 *
	 * A watched field may come later in the field order and not be duplicated yet, so any id that
	 * cannot be switched now is left for the second pass in
	 * FrmProForm::maybe_fix_field_ids_after_duplicate().
	 *
	 * @since 6.34
	 *
	 * @param array $frm_duplicate_ids Map of original field ids to their new ids.
	 * @param array $values            Field values, passed by reference.
	 *
	 * @return void
	 */
	private static function switch_watch_lookup_ids( $frm_duplicate_ids, &$values ) {
		if ( empty( $values['field_options']['watch_lookup'] ) || ! is_array( $values['field_options']['watch_lookup'] ) ) {
			return;
		}

		$has_unresolved_id = false;

		foreach ( $values['field_options']['watch_lookup'] as $key => $old_id ) {
			if ( isset( $frm_duplicate_ids[ $old_id ] ) ) {
				$values['field_options']['watch_lookup'][ $key ] = $frm_duplicate_ids[ $old_id ];
			} elseif ( $old_id ) {
				$has_unresolved_id = true;
			}
		}

		if ( $has_unresolved_id ) {
			self::mark_field_key_as_unprocessed( $values['field_key'] );
		}
	}

	/**
	 * Switch the field ID for a given setting if a new field ID exists
	 *
	 * @since 2.01.0
	 *
	 * @param array $frm_duplicate_ids
	 * @param string $setting
	 * @param array $field_options
	 */
	private static function maybe_switch_field_id_in_setting( $frm_duplicate_ids, $setting, &$field_options ) {
		$old_field_id = $field_options[ $setting ] ?? 0;

		if ( ! $old_field_id ) {
			return;
		}

		if ( is_array( $old_field_id ) ) {
			$field_options[ $setting ] = array();

			foreach ( $old_field_id as $old_id ) {
				$field_options[ $setting ][] = $frm_duplicate_ids[ $old_id ] ?? $old_id;
			}
		} elseif ( isset( $frm_duplicate_ids[ $old_field_id ] ) ) {
			$field_options[ $setting ] = $frm_duplicate_ids[ $old_field_id ];
		}
	}

	public static function delete( $id ) {
		$field = FrmField::getOne( $id );

		if ( ! $field ) {
			return;
		}

		// Delete the form this repeating field created
		self::delete_repeat_field( $field );
		self::reset_form_transition_if_no_break_field( $field );

		// TODO: before delete do something with entries with data field meta_value = field_id
	}

	public static function delete_repeat_field( $field ) {
		if ( ! FrmField::is_repeating_field( $field ) ) {
			return;
		}

		if ( isset( $field->field_options['form_select'] ) && is_numeric( $field->field_options['form_select'] ) && $field->field_options['form_select'] != $field->form_id ) {
			FrmForm::destroy( $field->field_options['form_select'] );
		}
	}

	/**
	 * Reset the form transition if there are no more break fields.
	 *
	 * @param object $field Field object.
	 *
	 * @return void
	 */
	private static function reset_form_transition_if_no_break_field( $field ) {
		if ( 'break' !== FrmField::get_field_type( $field ) ) {
			return;
		}

		$remaining_break_fields = FrmDb::get_count(
			'frm_fields',
			array(
				'form_id' => $field->form_id,
				'type'    => 'break',
				'id !'    => $field->id,
			)
		);

		if ( $remaining_break_fields > 0 ) {
			return;
		}

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

		if ( ! $form ) {
			return;
		}

		global $wpdb;
		$form->options['transition'] = '';

		// Use custom query instead of FrmForm::update() to prevent duplicate code runs.
		$wpdb->update(
			$wpdb->prefix . 'frm_forms',
			array( 'options' => serialize( $form->options ) ),
			array( 'id' => $field->form_id )
		);

		FrmForm::clear_form_cache();
	}

	/**
	 * @param stdClass $field
	 *
	 * @return bool
	 */
	public static function is_list_field( $field ) {
		return $field->type === 'data' && ( ! isset( $field->field_options['data_type'] ) || $field->field_options['data_type'] === 'data' || $field->field_options['data_type'] == '' );
	}

	/**
	 * Create the form for a repeating section
	 *
	 * @since 2.0.12
	 *
	 * @param int $form_id
	 * @param array $atts
	 *
	 * @return int Form ID.
	 */
	public static function create_repeat_form( $form_id, $atts ) {
		$form_values = array(
			'parent_form_id' => $atts['parent_form_id'],
			'name'           => $atts['field_name'],
			'status'         => 'published',
		);
		$form_values = FrmFormsHelper::setup_new_vars( $form_values );

		return (int) FrmForm::create( $form_values );
	}

	/**
	 * Return all the field IDs for the fields inside of a section (not necessarily repeating) or an embedded form
	 *
	 * @since 2.0.13
	 *
	 * @param array $field
	 *
	 * @return array Children.
	 */
	public static function get_children( $field ) {
		if ( FrmField::is_repeating_field( $field ) || $field['type'] === 'form' ) {
			// If repeating field or embedded form

			$repeat_id = $field['form_select'] ?? $field['field_options']['form_select'];
			return FrmDb::get_col( 'frm_fields', array( 'form_id' => $repeat_id ) );
		}

		// If regular section
		return self::get_children_from_standard_section( $field );
	}

	/**
	 * Get the field IDs within a regular section
	 *
	 * @since 2.0.25
	 *
	 * @param array $field
	 *
	 * @return array|null
	 */
	private static function get_children_from_standard_section( $field ) {
		$child_where = array( 'form_id' => $field['form_id'] );

		// Get minimum field order for children
		$min_field_order             = $field['field_order'] + 1;
		$child_where['field_order>'] = $min_field_order;

		// Get maximum field order for children
		$where             = array(
			'form_id'      => $field['form_id'],
			'type'         => array( 'end_divider', 'divider', 'break' ),
			'field_order>' => $min_field_order,
		);
		$end_divider_order = FrmDb::get_var( 'frm_fields', $where, 'field_order', array( 'order_by' => 'field_order ASC' ), 1 );

		if ( $end_divider_order ) {
			$child_where['field_order<'] = $end_divider_order - 1;
		}

		return FrmDb::get_col( 'frm_fields', $child_where );
	}

	/**
	 * Get the entry ID from a linked field
	 *
	 * @since 2.0.15
	 *
	 * @param int $linked_field_id
	 * @param string $where_val
	 * @param string $where_is
	 *
	 * @return int Linked ID.
	 */
	public static function get_dynamic_field_entry_id( $linked_field_id, $where_val, $where_is ) {
		$query = array(
			'field_id' => $linked_field_id,
			'meta_value' . FrmDb::append_where_is( $where_is ) => $where_val,
		);
		return FrmDb::get_col( 'frm_item_metas', $query, 'item_id' );
	}

	/**
	 * Get the category ID from the category name
	 *
	 * @since 2.0.15
	 *
	 * @param string $cat_name
	 *
	 * @return int
	 */
	public static function get_cat_id_from_text( $cat_name ) {
		return get_cat_ID( $cat_name );
	}

	/**
	 * Check if the format option isset and true without a regular expression
	 *
	 * @since 2.02.06
	 *
	 * @param array|object $field
	 *
	 * @return bool
	 */
	public static function is_format_option_true_with_no_regex( $field ) {
		if ( is_array( $field ) ) {
			return FrmField::is_option_true_in_array( $field, 'format' ) && ! str_starts_with( $field['format'], '^' );
		}

		return FrmField::is_option_true_in_object( $field, 'format' ) && ! str_starts_with( $field->field_options['format'], '^' );
	}

	/**
	 * Get a list of field types that cannot be used in calculations.
	 *
	 * @since 4.0
	 *
	 * @return array
	 */
	public static function exclude_from_calcs() {
		$exclude   = FrmField::no_save_fields();
		$exclude[] = 'toggle';
		$exclude[] = 'data|select';
		$exclude[] = 'data|radio';
		$exclude[] = 'data|checkbox';
		$exclude[] = 'virtual';
		return $exclude;
	}

	/**
	 * Get a list of field types that can be used in numeric calculations.
	 *
	 * @since 6.34
	 *
	 * @return array
	 */
	public static function numeric_calculation_field_types() {
		$field_types = array(
			'number',
			'scale',
			'star',
			'range',
			'nps',
			'product',
			'quantity',
			'total',
			'radio',
			'dropdown',
			'checkbox',
			'hidden',
			'data',
			'lookup',
			'text',
			'textarea',
		);

		/**
		 * Filters the field types that can be used in numeric calculations.
		 *
		 * @since 6.34
		 *
		 * @param array $field_types Array of field type slugs.
		 */
		return apply_filters( 'frm_numeric_calculation_field_types', $field_types );
	}
}
