"use strict";

const getFieldsValues = (fields) => {
	const values = {};
	for (const id in fields) {
		let $inputs = fields[id].inputs;
		if ($inputs.length === 1) {
			let type = $inputs.attr("type");
			if (type === "checkbox" || type === "radio") {
				if ($inputs.prop("checked")) {
					values[id] = $inputs.val();
				} else {
					values[id] = "";
				}
			} else {
				values[id] = $inputs.val();
			}
		} else {
			// field with multiple inputs, like checkbox:
			let checked = [];
			$inputs.each((_, e) => {
				if (e.checked) {
					checked.push(e.value);
				}
			});
			if ($inputs.attr("type") === "radio") {
				// unwrap value from array if type is radio:
				values[id] = checked[0];
			} else {
				// it's a list of checkboxes:
				values[id] = checked.length ? checked : "";
			}
		}
	}
	return values;
};

// Returns a JS object with the HTML ids of all the input fields as keys and the
// jQuery input elements and wrapper elements obtained from the ids as values.
const getAllFields = ($form, fieldIds) => {
	const fields = {};
	for (const id of fieldIds) {
		const $group = $form.find(".elementor-field-group-" + id);
		if ($group.length) {
			fields[id] = { wrapper: $group };
			let $field = $group.find(`[name=form_fields\\[${id}\\]]`);
			if ($field.length) {
				// it's a simple field with only one input.
				fields[id].inputs = $field;
			} else {
				// multiple inputs, like in the case of the checkbox field:
				fields[id].inputs = $group.find(
					`[name=form_fields\\[${id}\\]\\[\\]]`,
				);
			}
		}
	}
	return fields;
};

// Receive a jQuery list of input elements. Returns true if they are all disabled.
const areAllFieldsDisabled = ($groups) => {
	for (const group of $groups) {
		if (group.dataset.dceConditionsFieldStatus !== "inactive") {
			return false;
		}
	}
	return true;
};

// If all the inputs in a step are inactive because of conditional-fields we
// want to automatically jump over them. We do this by adding handlers to the
// next and previous buttons. The handler for next buttons will check if all the
// field on the next step are disabled, if so it will trigger the click of the
// next button of the next step. Same but opposite for previous buttons.
const initializeStepsJumping = ($form) => {
	if (!$form.hasClass("dce-form-has-conditions")) {
		return; // no conditions, nothing to do.
	}
	let $steps = $form.find(".elementor-field-type-step");
	if ($steps.length < 3) {
		return; // two steps or less, nothing to jump over.
	}
	let steps = [];
	for (const step of $steps) {
		const $step = jQuery(step);
		// immediate child > otherwise you'll also get the step buttons
		const $fieldGroups = $step.find("> .elementor-field-group");
		const $next = $step.find(".elementor-field-type-next button");
		const $previous = $step.find(".elementor-field-type-previous button");
		steps.push({
			nextButton: $next,
			previousButton: $previous,
			fieldGroups: $fieldGroups,
		});
	}
	// Set click handlers for next buttons:
	// length - 2 because we never jump over the last step.
	for (let i = 0; i < steps.length - 2; i++) {
		const nextStep = steps[i + 1];
		steps[i].nextButton.on("click", () => {
			if (areAllFieldsDisabled(nextStep.fieldGroups)) {
				nextStep.nextButton.trigger("click");
			}
		});
	}
	// Set click handlers for previous buttons:
	// i = 2 because we never jump over the first step.
	for (let i = 2; i < steps.length; i++) {
		const prevStep = steps[i - 1];
		steps[i].previousButton.on("click", () => {
			if (areAllFieldsDisabled(prevStep.fieldGroups)) {
				prevStep.previousButton.trigger("click");
			}
		});
	}
};

const getOnFormChange = ({
	fields,
	field_conditions,
	submit_conditions,
	$form,
	lang,
}) => {
	const $submitButtonWrapper = $form.find(".elementor-field-type-submit");
	const $submitButton = $submitButtonWrapper.find("button");
	// disable the input elements of the given field, also hide it if disableOnly is not set.
	const deactivateField = (id, disableOnly) => {
		const $fieldInputs = fields[id].inputs;
		const $fieldWrapper = fields[id].wrapper;
		const isFileInput = $fieldInputs.length === 1 && $fieldInputs.attr("type") === "file";
		if (isFileInput) {
			// For file inputs, don't disable - we'll clear them on submit instead.
			// Disabling would prevent the field from being submitted at all,
			// causing Elementor's validation to fail with a count() error on NULL.
			// But we must remove 'required' to prevent browser validation error on hidden field.
			if ($fieldInputs.prop("required")) {
				$fieldInputs.data("dce-was-required", true);
				$fieldInputs.prop("required", false);
			}
		} else {
			$fieldInputs.prop("disabled", true);
		}
		$fieldWrapper[0].dataset.dceConditionsFieldStatus = "inactive";
		if (!disableOnly) {
			$fieldWrapper.hide();
		}
	};
	// enable the input elements of the given field, show it in case it was hidden.
	const activateField = (id, disableOnly) => {
		const $fieldInputs = fields[id].inputs;
		const $fieldWrapper = fields[id].wrapper;
		const isFileInput = $fieldInputs.length === 1 && $fieldInputs.attr("type") === "file";
		$fieldInputs.prop("disabled", false);
		// Restore required attribute for file inputs if it was removed
		if (isFileInput && $fieldInputs.data("dce-was-required")) {
			$fieldInputs.prop("required", true);
		}
		$fieldWrapper[0].dataset.dceConditionsFieldStatus = "active";
		// show only if it's not a field that should
		// always be hidden, like an hidden amount:
		if ($fieldInputs.data("hide") !== "yes") {
			$fieldWrapper.show();
			// Trigger custom event when field becomes visible
			$fieldWrapper.trigger("dce-field-shown", [id]);
		}
	};
	const handleFieldConditions = (values) => {
		for (const cond of field_conditions) {
			let result;
			try {
				result = lang.evaluate(cond.condition, values);
			} catch (error) {
				$form.prepend(`
<div class="error">
Conditional Fields Error (the error is on the conditions of field "<code>${cond.id}</code>"): ${error}
</div>`);
			}
			const isActive = cond.mode === "show" ? result : !result;
			if (!fields[cond.id]) {
				console.warn(
					"Conditional Fields, could not find field " + cond.id,
				);
				continue;
			}
			if (isActive) {
				activateField(cond.id, cond.disableOnly);
			} else {
				// if inactive we don't want its value to influence further conditions:
				values[cond.id] = "";
				deactivateField(cond.id, cond.disableOnly);
			}
		}
	};
	const handleSubmitConditions = (values) => {
		let hasDisableCondition = false;
		let isDisabled = false;
		let isHidden = false;
		for (const cond of submit_conditions) {
			let result;
			try {
				result = lang.evaluate(cond.expression, values);
			} catch (error) {
				$form.prepend(`
<div class="error">
Conditional Fields Error (the error is on the validation condition <code>${cond}</code>"): ${error}
</div>`);
			}
			if (cond.hide === "disable") {
				hasDisableCondition = true;
			}
			if (!result) {
				if (cond.hide === "hide") {
					isHidden = true;
					$submitButtonWrapper.hide();
				} else if (cond.hide === "disable") {
					isDisabled = true;
					$submitButton.prop("disabled", true);
				}
			}
		}
		if (!isHidden) {
			$submitButtonWrapper.show();
		}
		if (hasDisableCondition && !isDisabled) {
			$submitButton.prop("disabled", false);
		}
	};
	const onChange = () => {
		const values = getFieldsValues(fields);
		handleFieldConditions(values);
		$form.find("form").trigger("conditions-done");
		handleSubmitConditions(values);
	};
	return onChange;
};

function initializeConditionalFields($form) {
	const $outWrapper = $form.find(".elementor-form-fields-wrapper");
	$form.find(".dce-conditions-js-error-notice").remove();
	let field_conditions = $outWrapper.attr("data-field-conditions");
	let submit_conditions = $outWrapper.attr("data-submit-conditions");
	let fieldIds = $outWrapper.attr("data-field-ids");
	if (field_conditions === undefined && submit_conditions === undefined) {
		return; // no conditional fields.
	}

	// Clear hidden file inputs just before form submission to prevent validation errors.
	// We do this on submit rather than when hiding to preserve the file selection
	// in case the field becomes visible again before submission.
	// Using capturing phase (true) ensures this runs before Elementor's bubbling handlers.
	const formElement = $form.find("form")[0];
	if (formElement) {
		formElement.addEventListener(
			"submit",
			function () {
				$form
					.find('.elementor-field-group[data-dce-conditions-field-status="inactive"] input[type="file"]')
					.val("");
			},
			true,
		);
	}

	$form.addClass("dce-form-has-conditions");
	const lang = new expressionLanguage.ExpressionLanguage(
		new expressionLanguage.ArrayAdapter(),
	);
	lang.register(
		"in_array",
		(e) => "false",
		(args, el, arr) => {
			if (!Array.isArray(arr)) {
				return arr === el;
			}
			return arr.includes(el);
		},
	);
	lang.register(
		"to_number",
		(e) => "false",
		(args, str) => {
			// convert string to number:
			let n = +str;
			if (isNan(n)) {
				n = 0;
			}
			return n;
		},
	);
	fieldIds = JSON.parse(fieldIds);
	field_conditions = JSON.parse(
		typeof field_conditions === "string" ? field_conditions : "[]",
	);
	submit_conditions = JSON.parse(
		typeof submit_conditions === "string" ? submit_conditions : "[]",
	);
	const fields = getAllFields($form, fieldIds);
	// Generate the function that is run after one of form fields is changed so
	// we can update visibilities.
	let onFormChange = getOnFormChange({
		fields: fields,
		field_conditions: field_conditions,
		submit_conditions: submit_conditions,
		$form: $form,
		lang: lang,
	});
	onFormChange();
	$form.on("change", onFormChange);
	// Reinitialize Flatpickr on date/time fields when they become visible.
	// On iOS, Flatpickr doesn't properly handle the removal of the disabled
	// attribute, so we need to destroy and recreate the picker.
	$form.on("dce-field-shown", (event) => {
		const $wrapper = jQuery(event.target);
		const $dateTimeFields = $wrapper.find(
			".elementor-date-field, .elementor-time-field",
		);
		$dateTimeFields.each((_, field) => {
			if (field._flatpickr) {
				const options = { ...field._flatpickr.config };
				field._flatpickr.destroy();
				field.flatpickr(options);
			}
		});
	});
}

jQuery(window).on("elementor/frontend/init", function () {
	elementorFrontend.hooks.addAction(
		"frontend/element_ready/form.default",
		initializeConditionalFields,
	);
	// We delay runninng the steps initialization so it is run after the steps
	// are initialized.  This is necessary with Elementor's improved asset
	// loading on.
	elementorFrontend.hooks.addAction(
		"frontend/element_ready/form.default",
		($scope) => setTimeout(() => initializeStepsJumping($scope), 2000),
	);
});
