/* eslint-disable jsdoc/no-undefined-types */
type JQueryInput = JQuery<
	HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>;

import debounce from 'lodash.debounce';

const $ = window.jQuery;

export default class GPIProperties {
	public ajaxUrl: string;
	public formId: number;
	public targetFieldId: number;
	public triggerFieldIds: number[];
	public ajaxRefreshNonce: string;

	public $form: JQuery<HTMLFormElement>;
	public $targetField: JQueryInput;

	private isHandlingFieldRefresh: boolean = false;

	constructor({
		ajaxUrl,
		formId,
		targetFieldId,
		triggerFieldIds,
		ajaxRefreshNonce,
	}: Pick<
		GPIProperties,
		| 'ajaxUrl'
		| 'formId'
		| 'targetFieldId'
		| 'triggerFieldIds'
		| 'ajaxRefreshNonce'
	>) {
		this.ajaxUrl = ajaxUrl;
		this.formId = formId;
		this.targetFieldId = targetFieldId;
		this.triggerFieldIds = triggerFieldIds;
		this.ajaxRefreshNonce = ajaxRefreshNonce;

		this.$form = $(`form#gform_${this.formId}`);
		this.$targetField = $(`#field_${this.formId}_${this.targetFieldId}`);

		window.gform.addAction(
			'gform_input_change',
			// eslint-disable-next-line @typescript-eslint/no-shadow
			(elem: JQueryInput, formId: string, fieldId: string) => {
				if (this.isHandlingFieldRefresh) {
					return;
				}

				if ($.inArray(parseInt(fieldId), this.triggerFieldIds) !== -1) {
					this.refresh({
						targetFieldId: this.targetFieldId,
						triggerFieldId: parseInt(fieldId),
						$targetField: this.$targetField,
						$triggerField: elem,
					});
				}
			}
		);

		// Trigger refresh on conversational next navigation.
		// See https://secure.helpscout.net/conversation/2717443844/71873#thread-8212574593
		document.addEventListener('gfcf/conversational/navigate/next', () => {
			for (const fieldId of this.triggerFieldIds) {
				if ($(`#field_${formId}_${fieldId}`).is(':visible')) {
					window.gf_input_change(
						$(`#input_${formId}_${fieldId}`),
						formId,
						fieldId
					);
				}
			}
		});
	}

	refresh = debounce(
		(args: {
			targetFieldId: number;
			triggerFieldId?: number;
			$targetField: JQueryInput;
			$triggerField?: JQueryInput;
			initialLoad?: boolean;
		}) => {
			this.refreshCallback(args);
		},
		50
	);

	getFormElement() {
		let $form = $(
			'input[name="is_submit_' +
				this.formId +
				'"], #gform_fields_' +
				this.formId
		).parents('form');

		/* Use entry form if we're in the Gravity Forms admin entry view. */
		if ($('#wpwrap #entry_form').length) {
			$form = $('#entry_form');
		}

		return $form;
	}

	isGravityView() {
		// check if this.getFormElement() has a parent class with gv-container
		return this.getFormElement().parents('.gv-container').length;
	}

	refreshCallback(args: {
		targetFieldId: number;
		triggerFieldId?: number;
		$targetField: JQueryInput;
		$triggerField?: JQueryInput;
		initialLoad?: boolean;
	}) {
		if (args.initialLoad && this.$targetField.is('.gfield_error')) {
			return;
		}

		const formData: { [input: string]: any } = {};

		// If the form is not found, try to find it by data-formid attribute.
		// This is useful when form is embeded as on the GravityView Single page view.
		if (this.isGravityView()) {
			this.$form = $('form[data-formid="' + this.formId + '"]');
		}

		this.$form.find('input, select, textarea').each(function () {
			let name = $(this).attr('name');

			if (!name) {
				return;
			}

			const isArrayField = name.match(/\[]$/);

			if (isArrayField) {
				name = name.replace(/\[]$/, '');

				if (typeof formData[name] === 'undefined') {
					formData[name] = [];
				}
			}

			const input = this as HTMLInputElement;

			if (
				['radio', 'checkbox'].indexOf(input.type) !== -1 &&
				!input.checked
			) {
				return;
			}

			if (!isArrayField) {
				formData[name] = $(this).val();
			} else {
				formData[name].push($(this).val());
			}
		});

		const data: {
			action: string;
			security: string;
			form_id: number;
			target_field_id: number;
			trigger_field_id?: number;
			[input: string]: any;
		} = {
			...formData,
			action: 'gpi_refresh_field',
			security: this.ajaxRefreshNonce,
			form_id: this.formId,
			target_field_id: args.targetFieldId,
			trigger_field_id: args.triggerFieldId,
			gpi_initial_property_refresh: args.initialLoad,
		};

		// Prevent AJAX-enabled forms from intercepting our AJAX request.
		delete data.gform_ajax;

		// GF WooCommerce Product Add-ons Support
		if (typeof data['add-to-cart'] !== 'undefined') {
			data.gform_submit = data.gform_old_submit;

			delete data['add-to-cart'];
			delete data.gform_old_submit;
			delete data.wc_gforms_product_type;
			delete data.product_id;
			delete data.wc_gforms_form_id;
			delete data.wc_gforms_next_page;
			delete data.wc_gforms_previous_page;
		}

		this.$targetField.addClass('gpi-refreshing-field');

		$.post(this.ajaxUrl, data).done((response) => {
			this.$targetField.removeClass('gpi-refreshing-field');

			/**
			 * Filter to enable/disable preserving values if something gets checked prior to the AJAX request resolving
			 * that way inputs don't lose their values or get unchecked.
			 *
			 * @since 1.0-beta-3.12
			 *
			 * @param {boolean} preserveValues Whether values should be preserved.
			 * @param {JQuery}  $targetField   Field with inventory that was refreshed.
			 * @param {JQuery}  $triggerField  Property field that caused the field with inventory to be refreshed.
			 * @param {boolean} initialLoad    Whether the field was refreshed on the initial load of the form.
			 * @param {Object}  requestData    Payload used to initiate the `gpi_refresh_field` request.
			 * @param {Object}  response       AJAX response from `gpi_refresh_field` admin-ajax.php action.
			 */
			const preserveValuesOnRefresh = window.gform.applyFilters(
				'gpi_preserve_values_on_refresh',
				true,
				this.$targetField,
				args.$triggerField,
				args.initialLoad,
				data,
				response
			);

			const previousValues: { [id: string]: any } = {};

			if (preserveValuesOnRefresh) {
				// @ts-ignore
				this.$targetField?.find(':input:enabled').each(function () {
					const id = $(this).prop('id');

					if (!id) {
						return;
					}

					const value = $(this).val();

					if ($(this).is(':radio') || $(this).is(':checkbox')) {
						if ($(this).is(':checked')) {
							previousValues[id] = value;
						}
					} else {
						previousValues[id] = value;
					}
				});
			}

			if (response.success) {
				/*
				 * Preserve field footers when used with Conversational Forms otherwise a JS error will occur.
				 */
				const conversationalFooter = this.$targetField.find(
					'.gform-conversational__field-footer'
				);

				this.$targetField.html(response.data.content);

				// Append Conversational Forms field footer if found.
				if (conversationalFooter.length) {
					this.$targetField.append(conversationalFooter);
				}
			}

			if (preserveValuesOnRefresh) {
				for (const [id, value] of Object.entries(previousValues)) {
					const $inputs = this.$targetField.find(`#${id}`);

					if ($inputs && value !== null && value !== '') {
						if ($inputs.is(':radio') || $inputs.is(':checkbox')) {
							$inputs.prop('checked', false);
							$inputs
								.filter(`[value="${value}"]`)
								.filter(':enabled')
								.prop('checked', true);
						} else {
							$inputs.filter(':enabled').val(value);
						}
					}
				}
			}

			/**
			 * Support JetSloth's Image Choices plugin
			 * https://jetsloth.com/support/gravity-forms-image-choices/
			 */
			if (this.$targetField.hasClass('image-choices-field')) {
				if (
					typeof (window as any).imageChoices_SetUpFields ===
					'function'
				) {
					(window as any).imageChoices_SetUpFields(this.formId);
				}
			}

			/* Recalculate prices if the target field is a price field. This ensures that prices are re-added to labels. */
			if (
				this.$targetField.hasClass('gfield_price') &&
				typeof window.gformCalculateTotalPrice === 'function'
			) {
				window.gformCalculateTotalPrice(data.form_id);
			}

			// Prevent infinite loop by blocking gform_input_change handler during field refresh callbacks
			this.isHandlingFieldRefresh = true;

			// Trigger change events on inputs inside the refreshed field. This is mostly for Populate Anything.
			this.$targetField.find(':input').trigger('change');

			/**
			 * Action fired after a field with inventory dependent on a property is refreshed.
			 *
			 * @since 1.0-beta-1.3
			 * @since 1.0-beta-3.0 Added requestData and response params.
			 *
			 * @param {JQuery}  $targetField  Field with inventory that was refreshed.
			 * @param {JQuery}  $triggerField Property field that caused the field with inventory to be refreshed.
			 * @param {boolean} initialLoad   Whether the field was refreshed on the initial load of the form.
			 * @param {Object}  requestData   Payload used to initiate the `gpi_refresh_field` request.
			 * @param {Object}  response      AJAX response from `gpi_refresh_field` admin-ajax.php action.
			 */
			window.gform.doAction(
				'gpi_field_refreshed',
				this.$targetField,
				args.$triggerField,
				args.initialLoad,
				data,
				response
			);

			this.isHandlingFieldRefresh = false;
		});
	}
}
