jingrow.provide("jingrow.ui.form");
jingrow.provide("jingrow.model.pageinfo");

import "./quick_entry";
import "./toolbar";
import "./dashboard";
import "./workflow";
import "./save";
import "./print_utils";
import "./success_action";
import "./script_manager";
import "./script_helpers";
import "./sidebar/form_sidebar";
import "./footer/footer";
import "./form_tour";
import { UndoManager } from "./undo_manager";

jingrow.ui.form.Controller = class FormController {
	constructor(opts) {
		$.extend(this, opts);
	}
};

jingrow.ui.form.Form = class JingrowForm {
	constructor(pagetype, parent, in_form, pagetype_layout_name) {
		this.pagename = "";
		this.pagetype = pagetype;
		this.pagetype_layout_name = pagetype_layout_name;
		this.in_form = in_form ? true : false;

		this.hidden = false;
		this.refresh_if_stale_for = 120;
		this.openpgs = {};
		this.custom_buttons = {};
		this.sections = [];
		this.grids = [];
		this.cscript = new jingrow.ui.form.Controller({ frm: this });
		this.events = {};
		this.fetch_dict = {};
		this.parent = parent;
		this.pagetype_layout = jingrow.get_pg("PageType Layout", pagetype_layout_name);
		this.undo_manager = new UndoManager({ frm: this });
		this.setup_meta(pagetype);
		this.debounced_reload_pg = jingrow.utils.debounce(this.reload_pg.bind(this), 1000);

		this.beforeUnloadListener = (event) => {
			event.preventDefault();
			// A String is returned for compatability with older Browsers. Return Value has to be truthy to trigger "Leave Site" Dialog
			return (event.returnValue =
				"There are unsaved changes, are you sure you want to exit?");
		};
	}

	setup_meta() {
		this.meta = jingrow.get_pg("PageType", this.pagetype);

		if (this.meta.istable) {
			this.meta.in_dialog = 1;
		}

		this.perm = jingrow.perm.get_perm(this.pagetype); // for create
		this.action_perm_type_map = {
			Create: "create",
			Save: "write",
			Submit: "submit",
			Update: "submit",
			Cancel: "cancel",
			Amend: "amend",
			Delete: "delete",
		};
	}

	setup() {
		this.fields = [];
		this.fields_dict = {};
		this.state_fieldname = jingrow.workflow.get_state_fieldname(this.pagetype);

		// wrapper
		this.wrapper = this.parent;
		this.$wrapper = $(this.wrapper);

		let is_single_column = this.pagetype === "PageType" ? true : this.meta.hide_toolbar;

		jingrow.ui.make_app_page({
			parent: this.wrapper,
			single_column: is_single_column,
		});
		this.page = this.wrapper.page;
		this.layout_main = this.page.main.get(0);

		this.$wrapper.on("hide", () => {
			this.script_manager.trigger("on_hide");
		});

		this.toolbar = new jingrow.ui.form.Toolbar({
			frm: this,
			page: this.page,
		});

		this.viewers = new jingrow.ui.form.FormViewers({
			frm: this,
			parent: $('<div class="form-viewers d-flex"></div>').prependTo(this.page.page_actions),
		});

		// navigate records keyboard shortcuts
		this.add_form_keyboard_shortcuts();

		// 2 column layout
		this.setup_std_layout();

		// client script must be called after "setup" - there are no fields_dict attached to the frm otherwise
		this.script_manager = new jingrow.ui.form.ScriptManager({
			frm: this,
		});
		this.script_manager.setup();
		this.watch_model_updates();

		if (!this.meta.hide_toolbar && jingrow.boot.desk_settings.timeline) {
			// this.footer_tab = new jingrow.ui.form.Tab(this.layout, {
			// 	label: __("Activity"),
			// 	fieldname: 'timeline'
			// });

			this.footer = new jingrow.ui.form.Footer({
				frm: this,
				parent: $("<div>").appendTo(this.page.main.parent()),
			});
			$("body").attr("data-sidebar", 1);
		}
		this.setup_file_drop();
		this.setup_pagetype_actions();
		this.setup_notify_on_rename();

		this.setup_done = true;
	}

	add_form_keyboard_shortcuts() {
		// Navigate to next record
		jingrow.ui.keys.add_shortcut({
			shortcut: "shift+ctrl+>",
			action: () => this.navigate_records(0),
			page: this.page,
			description: __("Go to next record"),
			ignore_inputs: true,
			condition: () => !this.is_new(),
		});

		// Navigate to previous record
		jingrow.ui.keys.add_shortcut({
			shortcut: "shift+ctrl+<",
			action: () => this.navigate_records(1),
			page: this.page,
			description: __("Go to previous record"),
			ignore_inputs: true,
			condition: () => !this.is_new(),
		});

		// Alternate for redo, main shortcut are present in toolbar.js
		jingrow.ui.keys.add_shortcut({
			shortcut: "shift+ctrl+z",
			action: () => this.undo_manager.redo(),
			page: this.page,
			description: __("Redo last action"),
			condition: () => !this.is_form_builder(),
		});
		jingrow.ui.keys.add_shortcut({
			shortcut: "ctrl+p",
			action: () => this.print_pg(),
			description: __("Print document"),
			condition: () => jingrow.model.can_print(this.pagetype, this) && !this.meta.issingle,
		});

		let grid_shortcut_keys = [
			{
				shortcut: "Up Arrow",
				description: __("Move cursor to above row"),
			},
			{
				shortcut: "Down Arrow",
				description: __("Move cursor to below row"),
			},
			{
				shortcut: "tab",
				description: __("Move cursor to next column"),
			},
			{
				shortcut: "shift+tab",
				description: __("Move cursor to previous column"),
			},
			{
				shortcut: "Ctrl+up",
				description: __("Add a row above the current row"),
			},
			{
				shortcut: "Ctrl+down",
				description: __("Add a row below the current row"),
			},
			{
				shortcut: "Ctrl+shift+up",
				description: __("Add a row at the top"),
			},
			{
				shortcut: "Ctrl+shift+down",
				description: __("Add a row at the bottom"),
			},
			{
				shortcut: "shift+alt+down",
				description: __("Duplicate current row"),
			},
		];

		grid_shortcut_keys.forEach((row) => {
			jingrow.ui.keys.add_shortcut({
				shortcut: row.shortcut,
				page: this.page,
				description: __(row.description),
				ignore_inputs: true,
				condition: () => !this.is_new(),
			});
		});
	}

	setup_std_layout() {
		this.form_wrapper = $("<div></div>").appendTo(this.layout_main);
		this.body = $('<div class="std-form-layout"></div>').appendTo(this.form_wrapper);

		// only tray
		this.meta.section_style = "Simple"; // always simple!

		// layout
		this.layout = new jingrow.ui.form.Layout({
			parent: this.body,
			pagetype: this.pagetype,
			pagetype_layout: this.pagetype_layout,
			frm: this,
			with_dashboard: true,
			card_layout: true,
		});

		this.layout.make();

		this.fields_dict = this.layout.fields_dict;
		this.fields = this.layout.fields_list;

		let dashboard_parent = $('<div class="form-dashboard">');
		let dashboard_added = false;

		if (this.layout.tabs.length) {
			this.layout.tabs.every((tab) => {
				if (tab.df.show_dashboard) {
					tab.wrapper.prepend(dashboard_parent);
					dashboard_added = true;
					return false;
				}
				return true;
			});
			if (!dashboard_added) {
				this.layout.tabs[0].wrapper.prepend(dashboard_parent);
			}
		} else {
			this.layout.wrapper.find(".form-page").prepend(dashboard_parent);
		}

		this.dashboard = new jingrow.ui.form.Dashboard(dashboard_parent, this);

		this.tour = new jingrow.ui.form.FormTour({
			frm: this,
		});

		// workflow state
		this.states = new jingrow.ui.form.States({
			frm: this,
		});
	}

	watch_model_updates() {
		// watch model updates
		var me = this;

		// on main pg
		jingrow.model.on(
			me.pagetype,
			"*",
			function (fieldname, value, pg, skip_dirty_trigger = false) {
				// set input
				if (pg.name == me.pagename) {
					if (!skip_dirty_trigger) {
						me.dirty();
					}

					let field = me.fields_dict[fieldname];
					field && field.refresh(fieldname);

					// Validate value for link field explicitly
					field &&
						["Link", "Dynamic Link"].includes(field.df.fieldtype) &&
						field.validate &&
						field.validate(value);

					me.layout.refresh_dependency();
					me.layout.refresh_sections();
					return me.script_manager.trigger(fieldname, pg.pagetype, pg.name);
				}
			}
		);

		// on table fields
		var table_fields = jingrow.get_children("PageType", me.pagetype, "fields", {
			fieldtype: ["in", jingrow.model.table_fields],
		});

		// using $.each to preserve df via closure
		$.each(table_fields, function (i, df) {
			jingrow.model.on(df.options, "*", function (fieldname, value, pg) {
				if (pg.parent == me.pagename && pg.parentfield === df.fieldname) {
					me.dirty();
					me.fields_dict[df.fieldname].grid.set_value(fieldname, value, pg);
					return me.script_manager.trigger(fieldname, pg.pagetype, pg.name);
				}
			});
		});
	}

	setup_notify_on_rename() {
		$(document).on("rename", (ev, dt, old_name, new_name) => {
			if (dt == this.pagetype) this.rename_notify(dt, old_name, new_name);
		});
	}

	setup_file_drop() {
		var me = this;
		this.$wrapper.on("dragenter dragover", false).on("drop", function (e) {
			var dataTransfer = e.originalEvent.dataTransfer;
			if (!(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0)) {
				return;
			}

			e.stopPropagation();
			e.preventDefault();

			if (me.pg.__islocal) {
				jingrow.msgprint(__("Please save before attaching."));
				throw "attach error";
			}

			new jingrow.ui.FileUploader({
				pagetype: me.pagetype,
				pagename: me.pagename,
				frm: me,
				files: dataTransfer.files,
				folder: "Home/Attachments",
				on_success(file_pg) {
					me.attachments.attachment_uploaded(file_pg);
				},
			});
		});
	}

	setup_image_autocompletions_in_markdown() {
		this.fields.map((field) => {
			if (field.df.fieldtype === "Markdown Editor") {
				this.set_df_property(field.df.fieldname, "autocompletions", () => {
					let attachments = this.attachments.get_attachments();
					return attachments
						.filter((file) => jingrow.utils.is_image_file(file.file_url))
						.map((file) => {
							return {
								caption: "image: " + file.file_name,
								value: `![](${file.file_url})`,
								meta: "image",
							};
						});
				});
			}
		});
	}

	// REFRESH

	refresh(pagename) {
		var switched = pagename ? true : false;

		removeEventListener("beforeunload", this.beforeUnloadListener, { capture: true });

		if (pagename) {
			this.switch_pg(pagename);
		}

		cur_frm = this;

		this.undo_manager.erase_history();

		if (this.pagename) {
			// document to show
			this.save_disabled = false;
			// set the pg
			this.pg = jingrow.get_pg(this.pagetype, this.pagename);

			// check permissions
			this.fetch_permissions();
			if (!this.has_read_permission()) {
				jingrow.show_not_permitted(__(this.pagetype) + " " + __(cstr(this.pagename)));
				return;
			}

			// update grids with new permissions
			this.grids.forEach((table) => {
				table.grid.refresh();
			});

			// read only (workflow)
			this.read_only = jingrow.workflow.is_read_only(this.pagetype, this.pagename);
			if (this.read_only) {
				this.set_read_only();
			}

			// check if pagetype is already open
			if (!this.openpgs[this.pagename]) {
				this.check_pagetype_conflict(this.pagename);
			} else {
				if (this.check_reload()) {
					return;
				}
			}

			// do setup
			if (!this.setup_done) {
				this.setup();
			}

			// load the record for the first time, if not loaded (call 'onload')
			this.trigger_onload(switched);

			if (switched) {
				if (this.show_print_first && this.pg.pagestatus === 1) {
					// show print view
					this.print_pg();
				}
			}

			// set status classes
			this.$wrapper
				.removeClass("validated-form")
				.toggleClass("editable-form", this.pg.pagestatus === 0)
				.toggleClass("submitted-form", this.pg.pagestatus === 1)
				.toggleClass("cancelled-form", this.pg.pagestatus === 2);

			this.show_conflict_message();
			this.show_submission_queue_banner();

			if (jingrow.boot.read_only) {
				this.disable_form();
			}
		}
	}

	// sets up the refresh event for custom buttons
	// added via configuration
	setup_pagetype_actions() {
		if (this.meta.actions) {
			for (let action of this.meta.actions) {
				jingrow.ui.form.on(this.pagetype, "refresh", () => {
					if (!this.is_new()) {
						if (!action.hidden) {
							this.add_custom_button(
								action.label,
								() => {
									this.execute_action(action);
								},
								action.group
							);
						}
					}
				});
			}
		}
	}

	execute_action(action) {
		if (typeof action === "string") {
			// called by label - maybe via custom script
			// frm.execute_action('Action')
			for (let _action of this.meta.actions) {
				if (_action.label === action) {
					action = _action;
					break;
				}
			}

			if (typeof action === "string") {
				jingrow.throw(`Action ${action} not found`);
			}
		}
		if (action.action_type === "Server Action") {
			return jingrow.xcall(action.action, { pg: this.pg }).then((pg) => {
				if (pg.pagetype) {
					// document is returned by the method,
					// apply the changes locally and refresh
					jingrow.model.sync(pg);
					this.refresh();
				}

				// feedback
				jingrow.msgprint({
					message: __("{} Complete", [__(action.label)]),
					alert: true,
				});
			});
		} else if (action.action_type === "Route") {
			return jingrow.set_route(action.action);
		}
	}

	switch_pg(pagename) {
		// reset visible columns, since column headings can change in different pgs
		this.grids.forEach((grid_obj) => {
			grid_obj.grid.visible_columns = null;
			// reset page number to 1
			grid_obj.grid.grid_pagination.go_to_page(1, true);
		});
		jingrow.ui.form.close_grid_form();
		this.viewers && this.viewers.parent.empty();
		this.pagename = pagename;
		this.setup_pageinfo_change_listener();
	}

	check_reload() {
		if (
			this.pg &&
			!this.pg.__unsaved &&
			this.pg.__last_sync_on &&
			new Date() - this.pg.__last_sync_on > this.refresh_if_stale_for * 1000
		) {
			this.debounced_reload_pg();
			return true;
		}
	}

	trigger_onload(switched) {
		this.cscript.is_onload = false;
		if (!this.openpgs[this.pagename]) {
			var me = this;
			this.cscript.is_onload = true;
			this.initialize_new_pg();
			$(document).trigger("form-load", [this]);
			$(this.page.wrapper).on("hide", function () {
				$(document).trigger("form-unload", [me]);
			});
		} else {
			this.render_form(switched);
			if (this.pg.localname) {
				// trigger form-rename and remove .localname
				delete this.pg.localname;
				$(document).trigger("form-rename", [this]);
			}
		}
	}

	initialize_new_pg() {
		var me = this;

		// hide any open grid
		this.script_manager.trigger("before_load", this.pagetype, this.pagename).then(() => {
			me.script_manager.trigger("onload");
			me.openpgs[me.pagename] = true;
			me.render_form();

			jingrow.after_ajax(function () {
				me.trigger_link_fields();
			});

			jingrow.breadcrumbs.add(me.meta.module, me.pagetype);
		});

		// update seen
		if (this.meta.track_seen) {
			$('.list-id[data-name="' + me.pagename + '"]').addClass("seen");
		}
	}

	render_form(switched) {
		if (!this.meta.istable) {
			this.layout.pg = this.pg;
			this.layout.attach_pg_and_pagefields();

			if (jingrow.boot.desk_settings.form_sidebar) {
				this.sidebar = new jingrow.ui.form.Sidebar({
					frm: this,
					page: this.page,
				});
				this.sidebar.make();
			}

			// clear layout message
			this.layout.show_message();

			jingrow.run_serially([
				// header must be refreshed before client methods
				// because add_custom_button
				() => this.refresh_header(switched),
				// trigger global trigger
				// to use this
				() => $(document).trigger("form-refresh", [this]),
				// fields
				() => this.refresh_fields(),
				// call trigger
				() => this.script_manager.trigger("refresh"),
				// call onload post render for callbacks to be fired
				() => {
					if (this.cscript.is_onload) {
						this.onload_post_render();
						return this.script_manager.trigger("onload_post_render");
					}
				},
				() => this.cscript.is_onload && this.is_new() && this.focus_on_first_input(),
				() => this.run_after_load_hook(),
				() => this.dashboard.after_refresh(),
			]);
		} else {
			this.refresh_header(switched);
		}

		this.$wrapper.trigger("render_complete");

		jingrow.after_ajax(() => {
			$(document).ready(() => {
				this.scroll_to_element();
			});
		});
	}

	onload_post_render() {
		this.setup_image_autocompletions_in_markdown();
	}

	focus_on_first_input() {
		const layout_wrapper = this.layout.wrapper;

		// dont do anything if the current active element is inside the form
		// user must have clicked on some element before this function trigerred
		if (!layout_wrapper || layout_wrapper.has(":focus").length) {
			return;
		}

		layout_wrapper
			.find(":input:visible:first")
			.not("[data-fieldtype^='Date']")
			.trigger("focus");
	}

	run_after_load_hook() {
		if (jingrow.route_hooks.after_load) {
			let route_callback = jingrow.route_hooks.after_load;
			delete jingrow.route_hooks.after_load;

			route_callback(this);
		}
	}

	refresh_fields() {
		this.layout.refresh(this.pg);
		this.layout.primary_button = this.$wrapper.find(".btn-primary");

		// cleanup activities after refresh
		this.cleanup_refresh(this);
	}

	cleanup_refresh() {
		if (this.fields_dict["amended_from"]) {
			if (this.pg.amended_from) {
				unhide_field("amended_from");
				if (this.fields_dict["amendment_date"]) unhide_field("amendment_date");
			} else {
				hide_field("amended_from");
				if (this.fields_dict["amendment_date"]) hide_field("amendment_date");
			}
		}

		if (this.fields_dict["trash_reason"]) {
			if (this.pg.trash_reason && this.pg.pagestatus == 2) {
				unhide_field("trash_reason");
			} else {
				hide_field("trash_reason");
			}
		}

		if (
			this.meta.autoname &&
			this.meta.autoname.substr(0, 6) == "field:" &&
			!this.pg.__islocal
		) {
			var fn = this.meta.autoname.substr(6);

			if (this.pg[fn]) {
				this.toggle_display(fn, false);
			}
		}

		if (this.meta.autoname == "naming_series:" && !this.pg.__islocal) {
			this.toggle_display("naming_series", false);
		}
	}

	refresh_header(switched) {
		// set title
		// main title
		if (!this.meta.in_dialog || this.in_form) {
			jingrow.utils.set_title(this.meta.issingle ? this.pagetype : this.pagename);
		}

		// show / hide buttons
		if (this.toolbar) {
			if (switched) {
				this.toolbar.current_status = undefined;
			}
			this.toolbar.refresh();
		}
		this.viewers.refresh();

		this.dashboard.refresh();
		jingrow.breadcrumbs.update();

		this.show_submit_message();
		this.clear_custom_buttons();
		this.show_web_link();
		this.show_workflow_read_only_banner();
	}

	// SAVE

	save_or_update() {
		if (this.save_disabled) return;

		if (this.pg.pagestatus === 0) {
			this.save();
		} else if (this.pg.pagestatus === 1 && this.pg.__unsaved) {
			this.save("Update");
		}
	}

	save(save_action, callback, btn, on_error) {
		let me = this;
		return new Promise((resolve, reject) => {
			btn && $(btn).prop("disabled", true);
			jingrow.ui.form.close_grid_form();
			me.validate_and_save(save_action, callback, btn, on_error, resolve, reject);
		})
			.then(() => {
				me.show_success_action();
			})
			.catch((e) => {
				console.error(e);
			});
	}

	validate_and_save(save_action, callback, btn, on_error, resolve, reject) {
		var me = this;
		if (!save_action) save_action = "Save";
		this.validate_form_action(save_action, resolve);

		var after_save = function (r) {
			// to remove hash from URL to avoid scroll after save
			history.replaceState(null, null, " ");
			if (!r.exc) {
				if (["Save", "Update", "Amend"].indexOf(save_action) !== -1) {
					jingrow.utils.play_sound("click");
				}

				me.script_manager.trigger("after_save");

				if (jingrow.route_hooks.after_save) {
					let route_callback = jingrow.route_hooks.after_save;
					delete jingrow.route_hooks.after_save;

					route_callback(me);
				}
				// submit comment if entered
				if (me.comment_box) {
					me.comment_box.submit();
				}
				me.refresh();
			} else {
				if (on_error) {
					on_error();
					reject();
				}
			}
			callback && callback(r);
			resolve();
		};

		var fail = (e) => {
			if (e) {
				console.error(e);
			}
			btn && $(btn).prop("disabled", false);
			if (on_error) {
				on_error();
				reject();
			}
		};

		if (save_action != "Update") {
			// validate
			jingrow.validated = true;
			jingrow
				.run_serially([
					() => this.script_manager.trigger("validate"),
					() => this.script_manager.trigger("before_save"),
					() => {
						if (!jingrow.validated) {
							fail();
							return;
						}

						jingrow.ui.form.save(me, save_action, after_save, btn);
					},
				])
				.catch(fail);
		} else {
			jingrow.ui.form.save(me, save_action, after_save, btn);
		}
	}

	savesubmit(btn, callback, on_error) {
		var me = this;
		return new Promise((resolve) => {
			this.validate_form_action("Submit");
			jingrow.confirm(
				__("Permanently Submit {0}?", [this.pagename]),
				function () {
					jingrow.validated = true;
					me.script_manager.trigger("before_submit").then(function () {
						if (!jingrow.validated) {
							return me.handle_save_fail(btn, on_error);
						}

						me.save(
							"Submit",
							function (r) {
								if (r.exc) {
									me.handle_save_fail(btn, on_error);
								} else {
									jingrow.utils.play_sound("submit");
									callback && callback();
									me.script_manager
										.trigger("on_submit")
										.then(() => resolve(me))
										.then(() => {
											if (jingrow.route_hooks.after_submit) {
												let route_callback =
													jingrow.route_hooks.after_submit;
												delete jingrow.route_hooks.after_submit;
												route_callback(me);
											}
										});
								}
							},
							btn,
							() => me.handle_save_fail(btn, on_error),
							resolve
						);
					});
				},
				() => me.handle_save_fail(btn, on_error)
			);
		});
	}

	savecancel(btn, callback, on_error) {
		const me = this;
		this.validate_form_action("Cancel");
		me.ignore_pagetypes_on_cancel_all = me.ignore_pagetypes_on_cancel_all || [];
		jingrow
			.call({
				method: "jingrow.desk.form.linked_with.get_submitted_linked_pgs",
				args: {
					pagetype: me.pg.pagetype,
					name: me.pg.name,
					ignore_pagetypes_on_cancel_all: me.ignore_pagetypes_on_cancel_all,
				},
				freeze: true,
			})
			.then((r) => {
				if (!r.exc) {
					let pagetypes_to_cancel = (r.message.pgs || []).map((value) => {
						return value.pagetype;
					});

					if (pagetypes_to_cancel.length) {
						return me._cancel_all(r, btn, callback, on_error);
					}
				}
				return me._cancel(btn, callback, on_error, false);
			});
	}

	_cancel_all(r, btn, callback, on_error) {
		const me = this;

		// add confirmation message for cancelling all linked pgs
		let links_text = "";
		let links = r.message.pgs;
		const pagetypes = Array.from(new Set(links.map((link) => link.pagetype)));

		me.ignore_pagetypes_on_cancel_all = me.ignore_pagetypes_on_cancel_all || [];

		for (let pagetype of pagetypes) {
			if (!me.ignore_pagetypes_on_cancel_all.includes(pagetype)) {
				let pagenames = links
					.filter((link) => link.pagetype == pagetype)
					.map((link) => jingrow.utils.get_form_link(link.pagetype, link.name, true))
					.join(", ");
				links_text += `<li><strong>${__(pagetype)}</strong>: ${pagenames}</li>`;
			}
		}
		links_text = `<ul>${links_text}</ul>`;

		let confirm_message = __("{0} {1} is linked with the following submitted documents: {2}", [
			__(me.pg.pagetype).bold(),
			me.pg.name,
			links_text,
		]);

		let can_cancel = links.every((link) => jingrow.model.can_cancel(link.pagetype));
		if (can_cancel) {
			confirm_message += __("Do you want to cancel all linked documents?");
		} else {
			confirm_message += __("You do not have permissions to cancel all linked documents.");
		}

		// generate dialog box to cancel all linked pgs
		let d = new jingrow.ui.Dialog(
			{
				title: __("Cancel All Documents"),
				fields: [
					{
						fieldtype: "HTML",
						options: `<p class="jingrow-confirm-message">${confirm_message}</p>`,
					},
				],
			},
			() => me.handle_save_fail(btn, on_error)
		);

		// if user can cancel all linked pgs, add action to the dialog
		if (can_cancel) {
			d.set_primary_action(__("Cancel All"), () => {
				d.hide();
				jingrow.call({
					method: "jingrow.desk.form.linked_with.cancel_all_linked_pgs",
					args: {
						pgs: links,
						ignore_pagetypes_on_cancel_all: me.ignore_pagetypes_on_cancel_all || [],
					},
					freeze: true,
					callback: (resp) => {
						if (!resp.exc) {
							me.reload_pg();
							me._cancel(btn, callback, on_error, true);
						}
					},
				});
			});
		}

		d.show();
	}

	_cancel(btn, callback, on_error, skip_confirm) {
		const me = this;
		const cancel_pg = () => {
			jingrow.validated = true;
			me.script_manager.trigger("before_cancel").then(() => {
				if (!jingrow.validated) {
					return me.handle_save_fail(btn, on_error);
				}

				var after_cancel = function (r) {
					if (r.exc) {
						me.handle_save_fail(btn, on_error);
					} else {
						jingrow.utils.play_sound("cancel");
						me.refresh();
						callback && callback();
						me.script_manager.trigger("after_cancel");
					}
				};
				jingrow.ui.form.save(me, "cancel", after_cancel, btn);
			});
		};

		if (skip_confirm) {
			cancel_pg();
		} else {
			jingrow.confirm(
				__("Permanently Cancel {0}?", [this.pagename]),
				cancel_pg,
				me.handle_save_fail(btn, on_error)
			);
		}
	}

	savetrash() {
		this.validate_form_action("Delete");
		jingrow.model.delete_pg(this.pagetype, this.pagename, function () {
			window.history.back();
		});
	}

	amend_pg() {
		if (!this.fields_dict["amended_from"]) {
			jingrow.msgprint(__('"amended_from" field must be present to do an amendment.'));
			return;
		}

		jingrow
			.xcall("jingrow.client.is_document_amended", {
				pagetype: this.pg.pagetype,
				pagename: this.pg.name,
			})
			.then((is_amended) => {
				if (is_amended) {
					jingrow.throw(
						__("This document is already amended, you cannot ammend it again")
					);
				}
				this.validate_form_action("Amend");
				var me = this;
				var fn = function (newdoc) {
					newdoc.amended_from = me.pagename;
					if (me.fields_dict && me.fields_dict["amendment_date"])
						newdoc.amendment_date = jingrow.datetime.obj_to_str(new Date());
				};
				this.copy_pg(fn, 1);
				jingrow.utils.play_sound("click");
			});
	}

	validate_form_action(action, resolve) {
		var perm_to_check = this.action_perm_type_map[action];
		var allowed_for_workflow = false;
		var perms = jingrow.perm.get_perm(this.pg.pagetype)[0];

		// Allow submit, write, cancel and create permissions for read only documents that are assigned by
		// workflows if the user already have those permissions. This is to allow for users to
		// continue through the workflow states and to allow execution of functions like Duplicate.
		if (
			(jingrow.workflow.is_read_only(this.pagetype, this.pagename) &&
				(perms["write"] || perms["create"] || perms["submit"] || perms["cancel"])) ||
			!jingrow.workflow.is_read_only(this.pagetype, this.pagename)
		) {
			allowed_for_workflow = true;
		}

		if (!this.perm[0][perm_to_check] && !allowed_for_workflow) {
			if (resolve) {
				// re-enable buttons
				resolve();
			}

			jingrow.throw(
				__(
					"No permission to '{0}' {1}",
					[__(action), __(this.pg.pagetype)],
					"{0} = verb, {1} = object"
				)
			);
		}
	}

	// HELPERS

	enable_save() {
		this.save_disabled = false;
		this.toolbar.set_primary_action();
	}

	disable_save(set_dirty = false) {
		// IMPORTANT: this function should be called in refresh event
		this.save_disabled = true;
		this.toolbar.current_status = null;
		// field changes should make form dirty
		this.set_dirty = set_dirty;
		this.page.clear_primary_action();
	}

	disable_form() {
		this.set_read_only();
		this.fields.forEach((field) => {
			this.set_df_property(field.df.fieldname, "read_only", "1");
		});
		this.disable_save();
	}

	handle_save_fail(btn, on_error) {
		$(btn).prop("disabled", false);
		if (on_error) {
			on_error();
		}
	}

	trigger_link_fields() {
		// trigger link fields which have default values set
		if (this.is_new() && this.pg.__run_link_triggers) {
			$.each(this.fields_dict, function (fieldname, field) {
				if (field.df.fieldtype == "Link" && this.pg[fieldname]) {
					// triggers add fetch, sets value in model and runs triggers
					field.set_value(this.pg[fieldname], true);
				}
			});

			delete this.pg.__run_link_triggers;
		}
	}

	show_conflict_message() {
		if (this.pg.__needs_refresh) {
			if (this.pg.__unsaved) {
				this.dashboard.clear_headline();
				this.dashboard.set_headline_alert(
					__("This form has been modified after you have loaded it") +
						'<button class="btn btn-xs btn-primary pull-right" onclick="cur_frm.reload_pg()">' +
						__("Refresh") +
						"</button>",
					"alert-warning"
				);
			} else {
				this.debounced_reload_pg();
			}
		}
	}

	show_submit_message() {
		if (
			this.meta.is_submittable &&
			this.perm[0] &&
			this.perm[0].submit &&
			!this.is_dirty() &&
			!this.is_new() &&
			!jingrow.model.has_workflow(this.pagetype) && // show only if no workflow
			this.pg.pagestatus === 0
		) {
			this.dashboard.add_comment(__("Submit this document to confirm"), "blue", true);
		}
	}

	show_web_link() {
		if (!this.pg.__islocal && this.pg.__onload && this.pg.__onload.is_website_generator) {
			this.web_link && this.web_link.remove();
			if (this.pg.__onload.published) {
				this.add_web_link("/" + this.pg.route);
			}
		}
	}

	add_web_link(path, label) {
		label = __(label) || __("See on Website");
		this.web_link = this.sidebar
			.add_user_action(__(label), function () {})
			.attr("href", path || this.pg.route)
			.attr("target", "_blank");
	}

	fetch_permissions() {
		let dt = this.parent_pagetype ? this.parent_pagetype : this.pagetype;
		this.perm = jingrow.perm.get_perm(dt, this.pg);
	}

	has_read_permission() {
		if (!this.perm[0].read) {
			return 0;
		}
		return 1;
	}

	check_pagetype_conflict(pagename) {
		if (this.pagetype == "PageType" && pagename == "PageType") {
			jingrow.msgprint(__("Allowing PageType, PageType. Be careful!"));
		} else if (this.pagetype == "PageType") {
			if (jingrow.views.formview[pagename] || jingrow.pages["List/" + pagename]) {
				window.location.reload();
			}
		} else {
			if (
				jingrow.views.formview.PageType &&
				jingrow.views.formview.PageType.frm.openpgs[this.pagetype]
			) {
				window.location.reload();
			}
		}
	}

	// rename the form
	// notify this form of renamed records
	rename_notify(dt, old, name) {
		// from form
		if (this.meta.istable) return;

		if (this.pagename == old) this.pagename = name;
		else return;

		// cleanup
		if (this && this.openpgs[old] && jingrow.meta.pagefield_copy[dt]) {
			// delete pagefield copy
			jingrow.meta.pagefield_copy[dt][name] = jingrow.meta.pagefield_copy[dt][old];
			delete jingrow.meta.pagefield_copy[dt][old];
		}

		delete this.openpgs[old];
		this.openpgs[name] = true;

		if (this.meta.in_dialog || !this.in_form) {
			return;
		}

		jingrow.re_route[jingrow.router.get_sub_path()] = `${encodeURIComponent(
			jingrow.router.slug(this.pagetype)
		)}/${encodeURIComponent(name)}`;
		!jingrow._from_link && jingrow.set_route("Form", this.pagetype, name);
	}

	// ACTIONS

	print_pg() {
		if (this.is_dirty()) {
			jingrow.toast({
				message: __(
					"This document has unsaved changes which might not appear in final PDF. <br> Consider saving the document before printing."
				),
				indicator: "yellow",
			});
		}

		jingrow.route_options = {
			frm: this,
		};
		jingrow.set_route("print", this.pagetype, this.pg.name);
	}

	navigate_records(prev) {
		let filters, sort_field, sort_order;
		let list_view = jingrow.get_list_view(this.pagetype);
		if (list_view) {
			filters = list_view.get_filters_for_args();
			sort_field = list_view.sort_by;
			sort_order = list_view.sort_order;
		} else {
			let list_settings = jingrow.get_user_settings(this.pagetype)["List"];
			if (list_settings) {
				filters = list_settings.filters;
				sort_field = list_settings.sort_by;
				sort_order = list_settings.sort_order;
			}
		}

		let args = {
			pagetype: this.pagetype,
			value: this.pagename,
			filters,
			sort_order,
			sort_field,
			prev,
		};

		jingrow.call("jingrow.desk.form.utils.get_next", args).then((r) => {
			if (r.message) {
				jingrow.set_route("Form", this.pagetype, r.message);
				this.focus_on_first_input();
			}
		});
	}

	rename_pg() {
		jingrow.model.rename_pg(this.pagetype, this.pagename, () => this.refresh_header());
	}

	share_pg() {
		this.shared.show();
	}

	email_pg(message) {
		new jingrow.views.CommunicationComposer({
			pg: this.pg,
			frm: this,
			subject: __(this.meta.name) + ": " + this.pagename,
			recipients: this.pg.email || this.pg.email_id || this.pg.contact_email,
			attach_document_print: true,
			message: message,
		});
	}

	copy_pg(onload, from_amend) {
		this.validate_form_action("Create");
		var newdoc = jingrow.model.copy_pg(this.pg, from_amend);

		newdoc.idx = null;
		newdoc.__run_link_triggers = false;
		if (onload) {
			onload(newdoc);
		}
		jingrow.set_route("Form", newdoc.pagetype, newdoc.name);
	}

	reload_pg() {
		this.check_pagetype_conflict(this.pagename);

		if (!this.pg.__islocal) {
			jingrow.model.remove_from_locals(this.pagetype, this.pagename);
			return jingrow.model.with_pg(this.pagetype, this.pagename, () => {
				this.refresh();
			});
		}
	}

	refresh_field(fname) {
		if (this.fields_dict[fname] && this.fields_dict[fname].refresh) {
			this.fields_dict[fname].refresh();
			this.layout.refresh_dependency();
			this.layout.refresh_sections();
		}
	}

	// UTILITIES
	add_fetch(link_field, source_field, target_field, target_pagetype) {
		/*
		Example fetch dict to get sender_email from email_id field in sender:
			{
				"Notification": {
					"sender": {
						"sender_email": "email_id"
					}
				}
			}
		*/

		if (!target_pagetype) target_pagetype = "*";

		// Target field kept as key because source field could be non-unique
		this.fetch_dict.setDefault(target_pagetype, {}).setDefault(link_field, {})[target_field] =
			source_field;
	}

	has_perm(ptype) {
		return jingrow.perm.has_perm(this.pagetype, 0, ptype, this.pg);
	}

	dirty() {
		this.pg.__unsaved = 1;
		this.$wrapper.trigger("dirty");
		if (!jingrow.boot.developer_mode) {
			addEventListener("beforeunload", this.beforeUnloadListener, { capture: true });
		}
	}

	get_pageinfo() {
		return jingrow.model.pageinfo[this.pagetype][this.pagename];
	}

	is_dirty() {
		return !!this.pg.__unsaved;
	}

	is_new() {
		return this.pg.__islocal;
	}

	is_form_builder() {
		return (
			["PageType", "Customize Form"].includes(this.pagetype) &&
			this.get_active_tab().label == "Form"
		);
	}

	get_perm(permlevel, access_type) {
		return this.perm[permlevel] ? this.perm[permlevel][access_type] : null;
	}

	set_intro(txt, color) {
		this.dashboard.set_headline_alert(txt, color);
	}

	set_footnote(txt) {
		this.footnote_area = jingrow.utils.set_footnote(this.footnote_area, this.body, txt);
	}

	add_custom_button(label, fn, group) {
		// temp! old parameter used to be icon
		if (group && group.indexOf("fa fa-") !== -1) group = null;

		let btn = this.page.add_inner_button(label, fn, group);

		if (btn) {
			// Add actions as menu item in Mobile View
			let menu_item_label = group ? `${group} > ${label}` : label;
			let menu_item = this.page.add_menu_item(menu_item_label, fn, false);
			menu_item.parent().addClass("hidden-xl");

			this.custom_buttons[label] = btn;
		}
		return btn;
	}

	change_custom_button_type(label, group, type) {
		this.page.change_inner_button_type(label, group, type);
	}

	clear_custom_buttons() {
		this.page.clear_inner_toolbar();
		this.page.clear_user_actions();
		this.custom_buttons = {};
	}

	//Remove specific custom button by button Label
	remove_custom_button(label, group) {
		this.page.remove_inner_button(label, group);
	}

	scroll_to_element() {
		if (jingrow.route_options && jingrow.route_options.scroll_to) {
			var scroll_to = jingrow.route_options.scroll_to;
			delete jingrow.route_options.scroll_to;

			var selector = [];
			for (var key in scroll_to) {
				var value = scroll_to[key];
				selector.push(repl('[data-%(key)s="%(value)s"]', { key: key, value: value }));
			}

			selector = $(selector.join(" "));
			if (selector.length) {
				jingrow.utils.scroll_to(selector);
			}
		} else if (window.location.hash) {
			if ($(window.location.hash).length) {
				jingrow.utils.scroll_to(window.location.hash, true, 200, null, null, true);
			} else {
				this.scroll_to_field(window.location.hash.replace("#", "")) &&
					history.replaceState(null, null, " ");
			}
		}
	}

	show_success_action() {
		const route = jingrow.get_route();
		if (route[0] !== "Form") return;
		if (this.meta.is_submittable && this.pg.pagestatus !== 1) return;

		const success_action = new jingrow.ui.form.SuccessAction(this);
		success_action.show();
	}

	get_pg() {
		return locals[this.pagetype][this.pagename];
	}

	set_currency_labels(fields_list, currency, parentfield) {
		// To set the currency in the label
		// For example Total Cost(INR), Total Cost(USD)
		if (!currency) return;
		var me = this;
		var pagetype = parentfield ? this.fields_dict[parentfield].grid.pagetype : this.pg.pagetype;
		var field_label_map = {};
		var grid_field_label_map = {};

		$.each(fields_list, function (i, fname) {
			var pagefield = jingrow.meta.pagefield_map[pagetype][fname];
			if (pagefield) {
				var label = __(pagefield.label || "", null, pagefield.parent).replace(
					/\([^\)]*\)/g,
					""
				); // eslint-disable-line
				if (parentfield) {
					grid_field_label_map[pagetype + "-" + fname] =
						label.trim() + " (" + __(currency) + ")";
				} else {
					field_label_map[fname] = label.trim() + " (" + currency + ")";
				}
			}
		});

		$.each(field_label_map, function (fname, label) {
			me.fields_dict[fname].set_label(label);
		});

		$.each(grid_field_label_map, function (fname, label) {
			fname = fname.split("-");
			me.fields_dict[parentfield].grid.update_pagefield_property(fname[1], "label", label);
		});
	}

	field_map(fnames, fn) {
		if (typeof fnames === "string") {
			if (fnames == "*") {
				fnames = Object.keys(this.fields_dict);
			} else {
				fnames = [fnames];
			}
		}
		for (var i = 0, l = fnames.length; i < l; i++) {
			var fieldname = fnames[i];
			var field = jingrow.meta.get_pagefield(this.pagetype, fieldname, this.pagename);
			if (field) {
				fn(field);
				this.refresh_field(fieldname);
			}
		}
	}

	get_pagefield(fieldname1, fieldname2) {
		if (fieldname2) {
			// for child
			var pagetype = this.get_pagefield(fieldname1).options;
			return jingrow.meta.get_pagefield(pagetype, fieldname2, this.pagename);
		} else {
			// for parent
			return jingrow.meta.get_pagefield(this.pagetype, fieldname1, this.pagename);
		}
	}

	set_df_property(fieldname, property, value, pagename, table_field, table_row_name = null) {
		let df;

		if (!pagename || !table_field) {
			df = this.get_pagefield(fieldname);
		} else {
			const grid = this.fields_dict[fieldname].grid;
			const filtered_fields = jingrow.utils.filter_dict(grid.pagefields, {
				fieldname: table_field,
			});
			if (filtered_fields.length) {
				df = jingrow.meta.get_pagefield(
					filtered_fields[0].parent,
					table_field,
					table_row_name
				);
			}
		}

		if (df && df[property] != value) {
			df[property] = value;

			if (table_field && table_row_name) {
				if (this.fields_dict[fieldname].grid.grid_rows_by_pagename[table_row_name]) {
					this.fields_dict[fieldname].grid.grid_rows_by_pagename[
						table_row_name
					].refresh_field(table_field);
				}
			} else {
				this.refresh_field(fieldname);
			}
		}
	}

	toggle_enable(fnames, enable) {
		this.field_map(fnames, function (field) {
			field.read_only = enable ? 0 : 1;
		});
	}

	toggle_reqd(fnames, mandatory) {
		this.field_map(fnames, function (field) {
			field.reqd = mandatory ? true : false;
		});
	}

	toggle_display(fnames, show) {
		this.field_map(fnames, function (field) {
			field.hidden = show ? 0 : 1;
		});
	}

	get_files() {
		return this.attachments
			? jingrow.utils.sort(this.attachments.get_attachments(), "file_name", "string")
			: [];
	}

	set_query(fieldname, opt1, opt2) {
		if (opt2) {
			// on child table
			// set_query(fieldname, parent fieldname, query)
			this.fields_dict[opt1].grid.get_field(fieldname).get_query = opt2;
		} else {
			// on parent table
			// set_query(fieldname, query)
			if (this.fields_dict[fieldname]) {
				this.fields_dict[fieldname].get_query = opt1;
			}
		}
	}

	clear_table(fieldname) {
		jingrow.model.clear_table(this.pg, fieldname);
	}

	add_child(fieldname, values) {
		var pg = jingrow.model.add_child(
			this.pg,
			jingrow.meta.get_pagefield(this.pagetype, fieldname).options,
			fieldname
		);
		if (values) {
			// Values of unique keys should not be overridden
			var d = {};
			var unique_keys = ["idx", "name"];

			Object.keys(values).map((key) => {
				if (!unique_keys.includes(key)) {
					d[key] = values[key];
				}
			});

			$.extend(pg, d);
		}
		return pg;
	}

	set_value(field, value, if_missing, skip_dirty_trigger = false) {
		var me = this;
		var _set = function (f, v) {
			var fieldobj = me.fields_dict[f];
			if (fieldobj) {
				if (!if_missing || !jingrow.model.has_value(me.pagetype, me.pg.name, f)) {
					if (
						jingrow.model.table_fields.includes(fieldobj.df.fieldtype) &&
						$.isArray(v)
					) {
						// set entire child table from specified array as value
						jingrow.model.clear_table(me.pg, fieldobj.df.fieldname);

						const standard_fields = [
							...jingrow.model.std_fields_list,
							...jingrow.model.child_table_field_list,
						];
						v.forEach((d, idx) => {
							let child = jingrow.model.add_child(
								me.pg,
								fieldobj.df.options,
								fieldobj.df.fieldname,
								idx + 1
							);

							// Don't set standard field, avoid mutating input too.
							let pg_copy = { ...d };
							standard_fields.forEach((field) => {
								delete pg_copy[field];
							});
							$.extend(child, pg_copy);
						});

						me.refresh_field(f);
						return Promise.resolve();
					} else {
						return jingrow.model.set_value(
							me.pagetype,
							me.pg.name,
							f,
							v,
							me.fieldtype,
							skip_dirty_trigger
						);
					}
				}
			} else {
				jingrow.msgprint(__("Field {0} not found.", [f]));
				throw "frm.set_value";
			}
		};

		if (typeof field == "string") {
			return _set(field, value);
		} else if ($.isPlainObject(field)) {
			let tasks = [];
			for (let f in field) {
				let v = field[f];
				if (me.get_field(f)) {
					tasks.push(() => _set(f, v));
				}
			}
			return jingrow.run_serially(tasks);
		}
	}

	call(opts, args, callback) {
		var me = this;
		if (typeof opts === "string") {
			// called as frm.call('do_this', {with_arg: 'arg'});
			opts = {
				method: opts,
				pg: this.pg,
				args: args,
				callback: callback,
			};
		}
		if (!opts.pg) {
			if (opts.method.indexOf(".") === -1)
				opts.method = jingrow.model.get_server_module_name(me.pagetype) + "." + opts.method;
			opts.original_callback = opts.callback;
			opts.callback = function (r) {
				if ($.isPlainObject(r.message)) {
					if (opts.child) {
						// update child pg
						opts.child = locals[opts.child.pagetype][opts.child.name];
						// if child row is deleted, don't update
						if (opts.child) {
							var std_field_list = ["pagetype"]
								.concat(jingrow.model.std_fields_list)
								.concat(jingrow.model.child_table_field_list);
							for (var key in r.message) {
								if (std_field_list.indexOf(key) === -1) {
									opts.child[key] = r.message[key];
								}
							}

							me.fields_dict[opts.child.parentfield].refresh();
						}
					} else {
						// update parent pg
						me.set_value(r.message);
					}
				}
				opts.original_callback && opts.original_callback(r);
			};
		} else {
			opts.original_callback = opts.callback;
			opts.callback = function (r) {
				if (!r.exc) me.refresh_fields();

				opts.original_callback && opts.original_callback(r);
			};
		}
		return jingrow.call(opts);
	}

	get_field(field) {
		return this.fields_dict[field];
	}

	set_read_only() {
		const pageperms = jingrow.perm.get_perm(this.pg.pagetype);
		this.perm = pageperms.map((p) => {
			return {
				read: p.read,
				cancel: p.cancel,
				share: p.share,
				print: p.print,
				email: p.email,
			};
		});
	}

	trigger(event, pagetype, pagename) {
		return this.script_manager.trigger(event, pagetype, pagename);
	}

	get_formatted(fieldname) {
		return jingrow.format(
			this.pg[fieldname],
			jingrow.meta.get_pagefield(this.pagetype, fieldname, this.pagename),
			{ no_icon: true },
			this.pg
		);
	}

	open_grid_row() {
		return jingrow.ui.form.get_open_grid_form();
	}

	get_title() {
		if (this.meta.title_field) {
			return this.pg[this.meta.title_field];
		} else {
			return String(this.pg.name);
		}
	}

	get_selected() {
		// returns list of children that are selected. returns [parentfield, name] for each
		var selected = {},
			me = this;
		jingrow.meta.get_table_fields(this.pagetype).forEach(function (df) {
			// handle TableMultiselect child fields
			let _selected = [];

			if (me.fields_dict[df.fieldname].grid) {
				_selected = me.fields_dict[df.fieldname].grid.get_selected();
			}

			if (_selected.length) {
				selected[df.fieldname] = _selected;
			}
		});
		return selected;
	}

	set_indicator_formatter(fieldname, get_color, get_text) {
		// get pagetype from parent
		var pagetype;
		if (jingrow.meta.pagefield_map[this.pagetype][fieldname]) {
			pagetype = this.pagetype;
		} else {
			jingrow.meta.get_table_fields(this.pagetype).every(function (df) {
				if (jingrow.meta.pagefield_map[df.options][fieldname]) {
					pagetype = df.options;
					return false;
				} else {
					return true;
				}
			});
		}

		jingrow.meta.pagefield_map[pagetype][fieldname].formatter = function (
			value,
			df,
			options,
			pg
		) {
			if (value) {
				var label;
				if (get_text) {
					label = get_text(pg);
				} else if (jingrow.form.link_formatters[df.options]) {
					label = jingrow.form.link_formatters[df.options](value, pg, df);
				} else {
					label = value;
				}

				const escaped_name = encodeURIComponent(value);

				return `
						<a class="indicator ${get_color(pg || {})}"
							href="/app/${jingrow.router.slug(df.options)}/${escaped_name}"
							data-pagetype="${df.options}"
							data-name="${jingrow.utils.escape_html(value)}">
							${label}
						</a>
					`;
			} else {
				return "";
			}
		};
	}

	can_create(pagetype) {
		// return true or false if the user can make a particlar pagetype
		// will check permission, `can_make_methods` if exists, or will decided on
		// basis of whether the document is submittable
		if (!jingrow.model.can_create(pagetype)) {
			return false;
		}

		if (this.custom_make_buttons && this.custom_make_buttons[pagetype]) {
			// custom buttons are translated and so are the keys
			const key = __(this.custom_make_buttons[pagetype]);
			// if the button is present, then show make
			return !!this.custom_buttons[key];
		}

		if (this.can_make_methods && this.can_make_methods[pagetype]) {
			return this.can_make_methods[pagetype](this);
		} else {
			if (this.meta.is_submittable && this.pg.pagestatus !== 1) {
				return false;
			} else {
				return true;
			}
		}
	}

	make_new(pagetype) {
		// make new pagetype from the current form
		// will handover to `make_methods` if defined
		// or will create and match link fields
		let me = this;
		if (this.make_methods && this.make_methods[pagetype]) {
			return this.make_methods[pagetype](this);
		} else if (this.custom_make_buttons && this.custom_make_buttons[pagetype]) {
			this.custom_buttons[__(this.custom_make_buttons[pagetype])].trigger("click");
		} else {
			jingrow.model.with_pagetype(pagetype, function () {
				let new_pg = jingrow.model.get_new_pg(pagetype, null, null, true);

				// set link fields (if found)
				me.set_link_field(pagetype, new_pg);

				jingrow.ui.form.make_quick_entry(pagetype, null, null, new_pg);
				// jingrow.set_route('Form', pagetype, new_pg.name);
			});
		}
	}

	set_link_field(pagetype, new_pg) {
		let me = this;
		jingrow.get_meta(pagetype).fields.forEach(function (df) {
			if (df.fieldtype === "Link" && df.options === me.pagetype) {
				new_pg[df.fieldname] = me.pg.name;
			} else if (["Link", "Dynamic Link"].includes(df.fieldtype) && me.pg[df.fieldname]) {
				new_pg[df.fieldname] = me.pg[df.fieldname];
			} else if (df.fieldtype === "Table" && df.options && df.reqd) {
				let row = new_pg[df.fieldname][0];
				me.set_link_field(df.options, row);
			}
		});
	}

	update_in_all_rows(table_fieldname, fieldname, value) {
		// Update the `value` of the field named `fieldname` in all rows of the
		// child table named `table_fieldname`.
		// Do not overwrite existing values.
		if (value === undefined) return;

		jingrow.model
			.get_children(this.pg, table_fieldname)
			.filter((child) => !jingrow.model.has_value(child.pagetype, child.name, fieldname))
			.forEach((child) =>
				jingrow.model.set_value(child.pagetype, child.name, fieldname, value)
			);
	}

	get_sum(table_fieldname, fieldname) {
		let sum = 0;
		for (let d of this.pg[table_fieldname] || []) {
			sum += d[fieldname];
		}
		return sum;
	}

	scroll_to_field(fieldname, focus = true) {
		let field = this.get_field(fieldname);
		if (!field) return;

		let $el = field.$wrapper;

		// set tab as active
		if (field.tab && !field.tab.is_active()) {
			field.tab.set_active();
		}

		// uncollapse section
		if (field.section?.is_collapsed()) {
			field.section.collapse(false);
		}

		// scroll to input
		jingrow.utils.scroll_to($el, true, 15);

		// focus if text field
		if (focus) {
			setTimeout(() => {
				$el.find("input, select, textarea").focus();
			}, 500);
		}

		// highlight control inside field
		let control_element = $el.closest(".jingrow-control");
		control_element.addClass("highlight");
		setTimeout(() => {
			control_element.removeClass("highlight");
		}, 2000);
		return true;
	}

	setup_pageinfo_change_listener() {
		let pagetype = this.pagetype;
		let pagename = this.pagename;

		if (this.pg && !this.is_new()) {
			jingrow.realtime.pg_subscribe(pagetype, pagename);
		}
		jingrow.realtime.off("pageinfo_update");
		jingrow.realtime.on("pageinfo_update", ({ pg, key, action = "update" }) => {
			if (
				!pg.reference_pagetype ||
				!pg.reference_name ||
				pg.reference_pagetype !== pagetype ||
				pg.reference_name !== pagename
			) {
				return;
			}
			let pg_list = jingrow.model.pageinfo[pagetype][pagename][key] || [];
			let docindex = pg_list.findIndex((old_pg) => {
				return old_pg.name === pg.name;
			});

			if (action === "add") {
				jingrow.model.pageinfo[pagetype][pagename][key].push(pg);
			}
			if (docindex > -1) {
				if (action === "update") {
					jingrow.model.pageinfo[pagetype][pagename][key].splice(docindex, 1, pg);
				}
				if (action === "delete") {
					jingrow.model.pageinfo[pagetype][pagename][key].splice(docindex, 1);
				}
			}
			// no need to update timeline of owner of comment
			// gets handled via comment submit code
			if (
				!(
					["add", "update"].includes(action) &&
					pg.pagetype === "Comment" &&
					pg.owner === jingrow.session.user
				)
			) {
				this.timeline && this.timeline.refresh();
			}
		});
	}

	// Filters fields from the reference pagetype and sets them as options for a Select field
	set_fields_as_options(
		fieldname,
		reference_pagetype,
		filter_function,
		default_options = [],
		table_fieldname
	) {
		if (!reference_pagetype) return Promise.resolve();
		let options = default_options || [];
		if (!filter_function) filter_function = (f) => f;
		return new Promise((resolve) => {
			jingrow.model.with_pagetype(reference_pagetype, () => {
				jingrow.get_meta(reference_pagetype).fields.map((df) => {
					filter_function(df) &&
						options.push({ label: df.label || df.fieldname, value: df.fieldname });
				});
				options &&
					this.set_df_property(
						fieldname,
						"options",
						options,
						this.pg.name,
						table_fieldname
					);
				resolve(options);
			});
		});
	}
	set_active_tab(tab) {
		if (!this.active_tab_map) {
			this.active_tab_map = {};
		}
		this.active_tab_map[this.pagename] = tab;

		this.script_manager.trigger("on_tab_change");
	}
	get_active_tab() {
		return this.active_tab_map && this.active_tab_map[this.pagename];
	}

	get_involved_users() {
		let user_fields = this.meta.fields
			.filter((d) => d.fieldtype === "Link" && d.options === "User")
			.map((d) => d.fieldname);

		user_fields = [...user_fields, "owner", "modified_by"];
		let involved_users = user_fields.map((field) => this.pg[field]);

		const pageinfo = this.get_pageinfo();

		involved_users = involved_users.concat(
			pageinfo.communications.map((d) => d.sender && d.delivery_status === "sent"),
			pageinfo.comments.map((d) => d.owner),
			pageinfo.versions.map((d) => d.owner),
			pageinfo.assignments.map((d) => d.owner)
		);

		return involved_users
			.uniqBy((u) => u)
			.filter((user) => !["Administrator", jingrow.session.user].includes(user))
			.filter(Boolean);
	}

	show_submission_queue_banner() {
		let wrapper = this.layout.wrapper.find(".submission-queue-banner");

		if (
			!(
				this.meta.is_submittable &&
				this.meta.queue_in_background &&
				!this.pg.__islocal &&
				this.pg.pagestatus === 0
			)
		) {
			wrapper.length && wrapper.remove();
			return;
		}

		if (!wrapper.length) {
			wrapper = $('<div class="submission-queue-banner form-message">');
			this.layout.wrapper.prepend(wrapper);
		}

		jingrow
			.call({
				method: "jingrow.core.pagetype.submission_queue.submission_queue.get_latest_submissions",
				args: { pagetype: this.pagetype, pagename: this.pagename },
			})
			.then((r) => {
				if (r.message?.latest_submission) {
					// if we are here that means some submission(s) were queued and are in queued/failed state
					let submission_label = __("Previous Submission");
					let secondary = "";
					let div_class = "col-md-12";

					if (r.message.exc) {
						secondary = `: <span>${r.message.exc}</span>`;
					} else {
						div_class = "col-md-6";
						secondary = `
						</div>
						<div class="col-md-6">
							<a href='/app/submission-queue?ref_pagetype=${encodeURIComponent(
								this.pagetype
							)}&ref_pagename=${encodeURIComponent(this.pagename)}'>${__(
							"All Submissions"
						)}</a>
						`;
					}

					let html = `
					<div class="row">
						<div class="${div_class}">
							<a href='/app/submission-queue/${r.message.latest_submission}'>${submission_label} (${r.message.status})</a>${secondary}
						</div>
					</div>
					`;

					wrapper.removeClass("red").removeClass("yellow");
					wrapper.addClass(r.message.status == "Failed" ? "red" : "yellow");
					wrapper.html(html);
				} else {
					wrapper.remove();
				}
			});
	}

	show_workflow_read_only_banner() {
		if (!this.read_only) {
			return;
		}

		const _show_read_only_banner = () => {
			this.dashboard.set_headline(
				__("This form is not editable due to a Workflow."),
				"blue",
				true
			);
		};

		if (this.dashboard) {
			_show_read_only_banner();
		} else {
			jingrow.after_ajax(_show_read_only_banner);
		}
	}
};

jingrow.validated = 0;
