let smarten_forms_debounce_timeout;
function smarten_forms() {
	if (smarten_forms_debounce_timeout) {
		clearTimeout(smarten_forms_debounce_timeout);
	}

	smarten_forms_debounce_timeout = setTimeout(() => {
		smarten_forms_debounce_timeout = undefined;

		const forms =
			document?.body?.querySelectorAll("form[data-smart]:not([data-smartened])") ?? [];
		for (const form of forms) {
			async function on_submit(event) {
				event.preventDefault();
				form.disabled = true;
				form.__submitted_at = new Date();

				if (form.on_submit) {
					const result = await form.on_submit(event);
					if (result === false) {
						form.disabled = false;
						return;
					}
				}

				const url = eval("`" + (form.attributes.url?.textContent ?? form.action) + "`");
				const method = form.dataset.method ?? "POST";

				const json = {};

				const form_data = new FormData(form);
				for (const [key, value] of form_data.entries()) {
					const input = form.querySelector(`[name="${key}"]`);

					if (input.dataset.skip) {
						continue;
					}

					if (input.type === "file") {
						if (input.dataset["smartformsSaveToHome"]) {
							form.uploaded = [];
							form.errors = [];

							const user = APP.user;
							if (!user) {
								throw new Error("You must be logged in to upload files here.");
							}

							for await (const file of input.files) {
								const body = new FormData();
								body.append("file", file, encodeURIComponent(file.name));

								const file_path =
									"/files/users/" + user.id + "/" + encodeURIComponent(file.name);

								const file_upload_response = await api.fetch(file_path, {
									method: "PUT",
									body,
								});

								if (!file_upload_response.ok) {
									const error = await file_upload_response.json();
									form.errors.push(error?.error?.message ?? "Unknown error.");
									continue;
								}

								const file_url =
									window.location.protocol +
									"//" +
									window.location.host +
									file_path;
								form.uploaded.push(file_url);
							}

							if (form.errors.length) {
								const errors = form.errors.join("\n\n");
								alert(errors);
								return false;
							}

							continue;
						}
					}

					if (key.length === 0) {
						continue;
					}

					const generator = input.getAttribute("generator");
					const generated_value =
						typeof generator === "string" && generator.length
							? eval(generator)(input, form)
							: undefined;

					const resolved_value =
						typeof value === "string" && value.length ? value : generated_value;

					if (typeof resolved_value === "undefined") {
						const should_submit_empty = input && input.dataset["smartformsSubmitEmpty"];
						if (!should_submit_empty) {
							continue;
						}
					}

					const elements = key.split(".");
					let current = json;
					for (const element of elements.slice(0, elements.length - 1)) {
						current[element] = current[element] ?? {};
						current = current[element];
					}

					current[elements.slice(elements.length - 1).shift()] = resolved_value;
				}

				if (form.uploaded?.length > 0) {
					json.data = json.data ?? {};
					json.data.media = [...form.uploaded];
				}

				const on_parsed =
					form.on_parsed ??
					(form.getAttribute("on_parsed")
						? eval(form.getAttribute("on_parsed"))
						: undefined);

				if (on_parsed) {
					await on_parsed(json);
				}

				try {
					const options = {
						method,
						headers: {
							Accept: "application/json",
						},
					};

					if (["POST", "PUT", "PATCH"].includes(method)) {
						options.json = json;
					}

					const response = await api.fetch(url, options);

					if (!response.ok) {
						const error_body = await response.json();
						const error = error_body?.error;

						if (form.on_error) {
							return form.on_error(error);
						}

						alert(error.message ?? "Unknown error:\n\n" + error);
						return;
					}

					if (form.on_response) {
						await form.on_response(response);
					}

					const response_body = await response.json();

					const on_reply =
						form.on_reply ??
						(form.getAttribute("on_reply")
							? eval(form.getAttribute("on_reply"))
							: undefined);
					if (on_reply) {
						try {
							await on_reply(response_body);
						} catch (error) {
							console.trace(error);
						}
					}

					const inputs_for_reset = form.querySelectorAll("[reset-on-submit]");
					for (const input of inputs_for_reset) {
						const reset_value = input.getAttribute("reset-on-submit");
						input.value = reset_value ?? "";
					}

					form.querySelector("[focus-on-submit]")?.focus();
				} catch (error) {
					console.dir({
						error,
					});

					if (form.on_error) {
						return form.on_error(error);
					}

					alert(error);
				} finally {
					form.disabled = false;
				}
			}

			form.querySelectorAll("[enter-key-submits]").forEach((element) => {
				element.addEventListener("keypress", (event) => {
					if (event.key === "Enter" && !event.shiftKey) {
						event.preventDefault();
						form.requestSubmit();
					}
				});
			});

			form.addEventListener("submit", on_submit);
			form.dataset.smartened = true;
		}
	}, 10);
}

const smarten_forms_observer = new MutationObserver(smarten_forms);
smarten_forms_observer.observe(document, {
	childList: true,
	subtree: true,
});
