/**
 * jingrow.views.ReportView
 */
import DataTable from "jingrow-datatable";

window.DataTable = DataTable;
jingrow.provide("jingrow.views");

jingrow.views.ReportView = class ReportView extends jingrow.views.ListView {
	get view_name() {
		return "Report";
	}

	render_header() {
		// Override List View Header
	}

	setup_defaults() {
		super.setup_defaults();
		this.page_title = __("Report:") + " " + this.page_title;
		this.view = "Report";

		const route = jingrow.get_route();
		if (route.length === 4) {
			this.report_name = route[3];
		}

		if (this.report_name) {
			return this.get_report_pg().then((pg) => {
				this.report_pg = pg;
				this.report_pg.json = JSON.parse(this.report_pg.json);

				this.filters = [
					...(Array.isArray(this.report_pg.json.filters)
						? this.report_pg.json.filters
						: []),
					...this.parse_filters_from_route_options(),
				];

				this.order_by = this.report_pg.json.order_by;
				this.add_totals_row = this.report_pg.json.add_totals_row;
				this.page_title = __(this.report_name);
				this.page_length = this.report_pg.json.page_length || 20;
				this.order_by = this.report_pg.json.order_by || "modified desc";
				this.chart_args = this.report_pg.json.chart_args;
			});
		} else {
			this.add_totals_row = this.view_user_settings.add_totals_row || 0;
			this.chart_args = this.view_user_settings.chart_args;
		}
		return this.get_list_view_settings();
	}

	setup_view() {
		this.setup_columns();
		super.setup_new_pg_event();
		this.setup_events();
		this.page.main.addClass("report-view");
	}

	setup_events() {
		if (this.list_view_settings?.disable_auto_refresh) {
			return;
		}
		jingrow.realtime.pagetype_subscribe(this.pagetype);
		jingrow.realtime.on("list_update", (data) => this.on_update(data));
	}

	setup_page() {
		this.menu_items = this.report_menu_items();
		super.setup_page();
	}

	toggle_side_bar() {
		super.toggle_side_bar();
		// refresh datatable when sidebar is toggled to accomodate extra space
		this.render(true);
	}

	setup_result_area() {
		super.setup_result_area();
		this.setup_charts_area();
		this.$datatable_wrapper = $('<div class="datatable-wrapper">');
		this.$result.append(this.$datatable_wrapper);
		this.settings.onload && this.settings.onload(this);
	}

	setup_charts_area() {
		this.$charts_wrapper = $(`<div class="charts-wrapper hidden">
			<div class="text-right"><button class="btn btn-default btn-xs btn-chart-configure"
				style="margin-right: 15px; margin-top: 15px">Configure</button></div>
			<div class="charts-inner-wrapper"></div>
		</div>`);
		this.$result.append(this.$charts_wrapper);
		this.$charts_wrapper.find(".btn-chart-configure").on("click", () => {
			this.setup_charts();
		});
	}

	setup_paging_area() {
		super.setup_paging_area();
		const message = __(
			"For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)."
		);
		this.$paging_area
			.find(".level-left")
			.after(`<span class="comparison-message text-muted">${message}</span>`);
	}

	setup_sort_selector() {
		this.sort_selector = new jingrow.ui.SortSelector({
			parent: this.filter_area.$filter_list_wrapper,
			pagetype: this.pagetype,
			args: this.order_by,
			onchange: this.on_sort_change.bind(this),
		});

		//Setup groupby for reports
		this.group_by_control = new jingrow.ui.GroupBy(this);
		if (this.report_pg && this.report_pg.json.group_by) {
			this.group_by_control.apply_settings(this.report_pg.json.group_by);
		}
		if (this.view_user_settings && this.view_user_settings.group_by) {
			this.group_by_control.apply_settings(this.view_user_settings.group_by);
		}
	}

	get_args() {
		const args = super.get_args();
		delete args.group_by;
		this.group_by_control.set_args(args);

		return args;
	}

	before_refresh() {
		if (this.report_pg) {
			// don't parse jingrow.route_options if this is a Custom Report
			return Promise.resolve();
		}
		return super.before_refresh();
	}

	after_render() {
		if (!this.report_pg) {
			this.save_report_settings();
		} else if (!$.isEmptyObject(this.report_pg.json)) {
			this.set_dirty_state_for_custom_report();
		}

		if (!this.group_by) {
			this.init_chart();
		}
	}

	set_dirty_state_for_custom_report() {
		let current_settings = {
			filters: this.filter_area.get(),
			fields: this.fields,
			order_by: this.sort_selector.get_sql_string(),
			add_totals_row: this.add_totals_row,
			page_length: this.page_length,
			column_widths: this.get_column_widths(),
			group_by: this.group_by_control.get_settings(),
			chart_args: this.get_chart_settings(),
		};

		let report_settings = {
			filters: this.report_pg.json.filters,
			fields: this.report_pg.json.fields,
			order_by: this.report_pg.json.order_by,
			add_totals_row: this.report_pg.json.add_totals_row,
			page_length: this.report_pg.json.page_length,
			column_widths: this.report_pg.json.column_widths,
			group_by: this.report_pg.json.group_by,
			chart_args: this.report_pg.json.chart_args,
		};

		if (!jingrow.utils.deep_equal(current_settings, report_settings)) {
			this.page.set_indicator(__("Not Saved"), "orange");
		} else {
			this.page.clear_indicator();
		}
	}

	save_report_settings() {
		jingrow.model.user_settings.save(this.pagetype, "last_view", this.view_name);

		if (!this.report_name) {
			this.save_view_user_settings({
				fields: this.fields,
				filters: this.filter_area.get(),
				order_by: this.sort_selector.get_sql_string(),
				group_by: this.group_by_control.get_settings(),
				chart_args: this.get_chart_settings(),
				add_totals_row: this.add_totals_row,
			});
		}
	}

	prepare_data(r) {
		let data = r.message || {};
		data = jingrow.utils.dict(data.keys, data.values);

		if (this.start === 0) {
			this.data = data;
		} else {
			this.data = this.data.concat(data);
		}
	}

	render(force) {
		if (this.data.length === 0) return;
		this.render_count();
		this.setup_columns();

		if (this.group_by) {
			this.$charts_wrapper.addClass("hidden");
		} else if (this.chart) {
			this.refresh_charts();
		}

		if (this.datatable && !force) {
			this.datatable.refresh(this.get_data(this.data), this.columns);
			return;
		}
		this.setup_datatable(this.data);
	}

	get_count_element() {
		let $count = this.$paging_area.find(".list-count");
		if (!$count.length) {
			$count = $("<span>")
				.addClass("text-muted list-count")
				.prependTo(this.$paging_area.find(".level-right"));
		}
		return $count;
	}

	on_update(data) {
		if (this.pagetype === data.pagetype && data.name) {
			// flash row when pg is updated by some other user
			const flash_row = data.user !== jingrow.session.user;
			if (this.data.find((d) => d.name === data.name)) {
				// update existing
				jingrow.db
					.get_pg(data.pagetype, data.name)
					.then((pg) => this.update_row(pg, flash_row));
			} else {
				// refresh
				this.refresh();
			}
		}
	}

	update_row(pg, flash_row) {
		const to_refresh = [];

		this.data = this.data.map((d, i) => {
			if (d.name === pg.name) {
				for (let fieldname in d) {
					if (fieldname.includes(":")) {
						// child table field
						const [cdt, _field] = fieldname.split(":");
						const cdt_row = Object.keys(pg)
							.filter(
								(key) =>
									Array.isArray(pg[key]) &&
									pg[key].length &&
									pg[key][0].pagetype === cdt
							)
							.map((key) => pg[key])
							.map((a) => a[0])
							.filter((cdoc) => cdoc.name === d[cdt + ":name"])[0];
						if (cdt_row) {
							d[fieldname] = cdt_row[_field];
						}
					} else {
						d[fieldname] = pg[fieldname];
					}
				}
				to_refresh.push([d, i]);
			}
			return d;
		});

		// indicate row update
		const _flash_row = (rowIndex) => {
			if (!flash_row) return;
			const $row = this.$result.find(`.dt-row[data-row-index="${rowIndex}"]`);
			$row.addClass("row-update");
			setTimeout(() => $row.removeClass("row-update"), 500);
		};

		to_refresh.forEach(([data, rowIndex]) => {
			const new_row = this.build_row(data);
			this.datatable.refreshRow(new_row, rowIndex);
			_flash_row(rowIndex);
		});
	}

	setup_datatable(values) {
		this.$datatable_wrapper.empty();
		this.datatable = new DataTable(this.$datatable_wrapper[0], {
			columns: this.columns,
			data: this.get_data(values),
			getEditor: this.get_editing_object.bind(this),
			language: jingrow.boot.lang,
			translations: jingrow.utils.datatable.get_translations(),
			checkboxColumn: true,
			inlineFilters: true,
			cellHeight: 35,
			direction: jingrow.utils.is_rtl() ? "rtl" : "ltr",
			events: {
				onRemoveColumn: (column) => {
					this.remove_column_from_datatable(column);
				},
				onSwitchColumn: (column1, column2) => {
					this.switch_column(column1, column2);
				},
				onCheckRow: () => {
					const checked_items = this.get_checked_items();
					this.toggle_actions_menu_button(checked_items.length > 0);
				},
			},
			hooks: {
				columnTotal: jingrow.utils.report_column_total,
			},
			headerDropdown: [
				{
					label: __("Add Column"),
					action: (datatabe_col) => {
						let columns_in_picker = [];
						const columns = this.get_columns_for_picker();

						columns_in_picker = columns[this.pagetype]
							.filter((df) => !this.is_column_added(df))
							.map((df) => ({
								label: __(df.label, null, df.parent),
								value: df.fieldname,
							}));

						delete columns[this.pagetype];

						for (let cdt in columns) {
							columns[cdt]
								.filter((df) => !this.is_column_added(df))
								.map((df) => ({
									label: __(df.label, null, df.parent) + ` (${cdt})`,
									value: df.fieldname + "," + cdt,
								}))
								.forEach((df) => columns_in_picker.push(df));
						}

						const d = new jingrow.ui.Dialog({
							title: __("Add Column"),
							fields: [
								{
									label: __("Select Column"),
									fieldname: "column",
									fieldtype: "Autocomplete",
									options: columns_in_picker,
								},
								{
									label: __("Insert Column Before {0}", [
										__(datatabe_col.pagefield.label).bold(),
									]),
									fieldname: "insert_before",
									fieldtype: "Check",
								},
							],
							primary_action: ({ column, insert_before }) => {
								if (!columns_in_picker.map((col) => col.value).includes(column)) {
									jingrow.show_alert({
										message: __("Invalid column"),
										indicator: "orange",
									});
									d.hide();
									return;
								}

								let pagetype = this.pagetype;
								if (column.includes(",")) {
									[column, pagetype] = column.split(",");
								}

								let index = datatabe_col.colIndex;
								if (insert_before) {
									index = index - 1;
								}

								this.add_column_to_datatable(column, pagetype, index);
								d.hide();
							},
						});

						d.show();
					},
				},
			],
		});
	}

	toggle_charts() {
		// add
		if (!this.chart) {
			this.setup_charts();
			return;
		}

		if (this.$charts_wrapper.hasClass("hidden")) {
			// reload chart
			this.$charts_wrapper.removeClass("hidden");
			this.refresh_charts();
		} else {
			// remove chart
			this.$charts_wrapper.addClass("hidden");
			this.save_view_user_settings({ chart_args: null });
			this.chart_args = null;
		}
	}

	init_chart() {
		// show chart if saved via report or user settings
		if (!this.chart) {
			if (this.chart_args) {
				this.build_chart_args(
					this.chart_args.x_axis,
					this.chart_args.y_axes,
					this.chart_args.chart_type
				);

				this.make_chart();
			}
		}
	}

	setup_charts() {
		// get fields from columns
		let x_fields = [],
			y_fields = [];
		for (let col of this.columns) {
			// all options in x
			x_fields.push({
				label: col.content,
				fieldname: col.id,
				value: col.id,
			});

			// numeric values in y
			if (col.pagefield && jingrow.model.numeric_fieldtypes.includes(col.pagefield.fieldtype)) {
				y_fields.push({
					label: col.content,
					fieldname: col.id,
					value: col.id,
				});
			}
		}

		const defaults = this.chart_args || {};

		const dialog = new jingrow.ui.Dialog({
			title: __("Configure Chart"),
			fields: [
				{
					label: __("X Axis Field"),
					fieldtype: "Select",
					fieldname: "x_axis",
					options: x_fields,
					default: defaults.x_axis,
				},
				{
					label: __("Y Axis Fields"),
					fieldtype: "MultiSelect",
					fieldname: "y_axes",
					options: y_fields,
					description: __("Showing only Numeric fields from Report"),
					default: defaults.y_axes ? defaults.y_axes.join(", ") : null,
				},
				{
					label: __("Chart Type"),
					fieldtype: "Select",
					options: ["Bar", "Line", "Pie", "Percentage", "Donut"],
					fieldname: "chart_type",
					default: defaults.chart_type
						? jingrow.utils.to_title_case(defaults.chart_type)
						: "Bar",
				},
			],
			primary_action: (data) => {
				data.y_axes = data.y_axes
					.split(",")
					.map((d) => d.trim())
					.filter(Boolean);

				this.build_chart_args(data.x_axis, data.y_axes, data.chart_type);
				this.make_chart();
				dialog.hide();
			},
		});

		dialog.show();
	}

	build_chart_args(x_axis, y_axes, chart_type) {
		let datasets = y_axes.map((y_axis) => ({
			name: this.columns_map[y_axis].content,
			values: this.data.map((d) => d[y_axis]),
		}));

		this.chart_args = {
			chart_type: chart_type.toLowerCase(),
			x_axis: x_axis,
			y_axes: y_axes,
			labels: this.data.map((d) => d[x_axis]),
			datasets: datasets,
		};

		this.save_view_user_settings({ chart_args: this.get_chart_settings() });
	}

	get_chart_settings() {
		if (this.chart_args) {
			return {
				chart_type: this.chart_args.chart_type,
				x_axis: this.chart_args.x_axis,
				y_axes: this.chart_args.y_axes,
			};
		}
	}

	make_chart() {
		const args = this.chart_args;
		let data = {
			labels: args.labels,
			datasets: args.datasets,
		};

		this.last_chart_type = args.chart_type;

		const get_df = (field) => this.columns_map[field].pagefield;

		this.$charts_wrapper.removeClass("hidden");

		this.chart = new jingrow.Chart(this.$charts_wrapper.find(".charts-inner-wrapper")[0], {
			title: __("{0} Chart", [this.pagetype]),
			data: data,
			type: args.chart_type,
			truncateLegends: 1,
			colors: ["#70E078", "light-blue", "orange", "red"],
			axisOptions: {
				shortenYAxisNumbers: 1,
				numberFormatter: jingrow.utils.format_chart_axis_number,
			},
			tooltipOptions: {
				formatTooltipY: (value) => {
					return jingrow.format(value, get_df(this.chart_args.y_axes[0]), {
						always_show_decimals: true,
						inline: true,
					});
				},
			},
		});
	}

	refresh_charts() {
		if (!this.chart || !this.chart_args) return;
		this.$charts_wrapper.removeClass("hidden");
		const { x_axis, y_axes, chart_type } = this.chart_args;
		this.build_chart_args(x_axis, y_axes, chart_type);
		this.chart.update(this.chart_args);
	}

	get_editing_object(colIndex, rowIndex, value, parent) {
		const control = this.render_editing_input(colIndex, value, parent);
		if (!control) return false;

		control.df.change = () => control.set_focus();

		return {
			initValue: (value) => {
				return control.set_value(value);
			},
			setValue: (value) => {
				const cell = this.datatable.getCell(colIndex, rowIndex);
				let fieldname = this.datatable.getColumn(colIndex).pagefield.fieldname;
				let pagename = cell.name;
				let pagetype = cell.pagetype;

				control.set_value(value);
				return this.set_control_value(pagetype, pagename, fieldname, value)
					.then((updated_pg) => {
						const _data = this.data
							.filter((b) => b.name === updated_pg.name)
							.find(
								(a) =>
									// child table cell
									(pagetype != updated_pg.pagetype &&
										a[pagetype + ":name"] == pagename) ||
									pagetype == updated_pg.pagetype
							);

						for (let field in _data) {
							if (field.includes(":")) {
								// child table field
								const [cdt, _field] = field.split(":");
								const cdt_row = Object.keys(updated_pg)
									.filter(
										(key) =>
											Array.isArray(updated_pg[key]) &&
											updated_pg[key].length &&
											updated_pg[key][0].pagetype === cdt
									)
									.map((key) => updated_pg[key])[0]
									.filter((cdoc) => cdoc.name === _data[cdt + ":name"])[0];
								if (cdt_row) {
									_data[field] = cdt_row[_field];
								}
							} else {
								_data[field] = updated_pg[field];
							}
						}
					})
					.then(() => this.refresh_charts());
			},
			getValue: () => {
				return control.get_value();
			},
		};
	}

	set_control_value(pagetype, pagename, fieldname, value) {
		this.last_updated_pg = pagename;
		return new Promise((resolve, reject) => {
			jingrow.db
				.set_value(pagetype, pagename, { [fieldname]: value })
				.then((r) => {
					if (r.message) {
						resolve(r.message);
					} else {
						reject();
					}
				})
				.fail(reject);
		});
	}

	render_editing_input(colIndex, value, parent) {
		const col = this.datatable.getColumn(colIndex);
		let control = null;

		if (col.pagefield.fieldtype === "Text Editor") {
			const d = new jingrow.ui.Dialog({
				title: __("Edit {0}", [col.pagefield.label]),
				fields: [col.pagefield],
				primary_action: () => {
					this.datatable.cellmanager.deactivateEditing();
					d.hide();
				},
				on_hide: () => {
					this.datatable.cellmanager.deactivateEditing(false);
				},
			});
			d.show();
			control = d.fields_dict[col.pagefield.fieldname];
		} else {
			// make control
			control = jingrow.ui.form.make_control({
				df: col.pagefield,
				parent: parent,
				render_input: true,
			});
			control.set_value(value);
			control.toggle_label(false);
			control.toggle_description(false);
		}

		return control;
	}

	evaluate_read_only_depends_on(expression, data) {
		let out = null;
		if (typeof expression === "boolean") {
			out = expression;
		} else if (expression.substr(0, 5) == "eval:") {
			try {
				out = jingrow.utils.eval(expression.substr(5), { pg: data });
				if (parent && parent.istable && expression.includes("is_submittable")) {
					out = true;
				}
			} catch (e) {
				jingrow.throw(__('Invalid "depends_on" expression'));
			}
		} else if (expression.substr(0, 3) == "fn:" && this.frm) {
			out = this.frm.script_manager.trigger(
				expression.substr(3),
				this.pagetype,
				this.pagename
			);
		} else {
			var value = data[expression];
			if ($.isArray(value)) {
				out = !!value.length;
			} else {
				out = !!value;
			}
		}
		return out;
	}

	is_editable(df, data) {
		if (
			df &&
			jingrow.model.can_write(this.pagetype) &&
			// not a submitted pg or field is allowed to edit after submit
			(data.pagestatus !== 1 || df.allow_on_submit) &&
			// not a cancelled pg
			data.pagestatus !== 2 &&
			!df.read_only &&
			!df.is_virtual &&
			!df.hidden &&
			// not a standard field i.e., owner, modified_by, etc.
			jingrow.model.is_non_std_field(df.fieldname)
		) {
			// don't check read_only_depends_on if there's child table fields
			return (
				this.meta.fields.some((df) => df.fieldtype === "Table") ||
				(df.read_only_depends_on &&
					!this.evaluate_read_only_depends_on(df.read_only_depends_on, data))
			);
		}
		return false;
	}

	get_data(values) {
		return this.build_rows(values);
	}

	set_fields() {
		// default fields
		["name", "pagestatus"].map((f) => this._add_field(f));

		if (this.report_name && this.report_pg.json.fields) {
			let fields = this.report_pg.json.fields.slice();
			fields.forEach((f) => this._add_field(f[0], f[1]));
			return;
		} else if (this.view_user_settings.fields) {
			// get from user_settings
			let fields = this.view_user_settings.fields;
			fields.forEach((f) => this._add_field(f[0], f[1]));
			return;
		}

		this.set_default_fields();
	}

	set_default_fields() {
		// get fields from meta
		this.fields = this.fields || [];
		const add_field = (f) => this._add_field(f);

		// default fields
		[this.meta.title_field, this.meta.image_field].map(add_field);

		// fields in_list_view or in_standard_filter
		const fields = this.meta.fields.filter((df) => {
			return (
				(df.in_list_view || df.in_standard_filter) &&
				jingrow.perm.has_perm(this.pagetype, df.permlevel, "read") &&
				jingrow.model.is_value_type(df.fieldtype) &&
				!df.report_hide
			);
		});

		fields.map(add_field);

		// currency fields
		fields
			.filter((df) => df.fieldtype === "Currency" && df.options)
			.map((df) => {
				if (df.options.includes(":")) {
					add_field(df.options.split(":")[1]);
				} else {
					add_field(df.options);
				}
			});

		// fields in listview_settings
		(this.settings.add_fields || []).map(add_field);
	}

	build_fields() {
		super.build_fields();
	}

	reorder_fields() {
		// generate table fields in the required format ["name", "PageType"]
		// these are fields in the column before adding new fields
		let table_fields = this.columns.map((df) => [df.field, df.pagefield.parent]);

		// filter fields that are already in table
		// iterate over table_fields to preserve the existing order of fields
		// The filter will ensure the unchecked fields are removed
		let fields_already_in_table = table_fields.filter((df) => {
			return this.fields.find((field) => {
				return df[0] == field[0] && df[1] == field[1];
			});
		});

		// find new fields that didn't already exists
		// This will be appended to the end of the table
		let fields_to_add = this.fields.filter((df) => {
			return !table_fields.find((field) => {
				return df[0] == field[0] && df[1] == field[1];
			});
		});

		// rebuild fields
		this.fields = [...fields_already_in_table, ...fields_to_add];
	}

	get_fields() {
		let fields = this.fields.map((f) => {
			let column_name = jingrow.model.get_full_column_name(f[0], f[1]);
			if (f[1] !== this.pagetype) {
				// child table field
				column_name = column_name + " as " + `'${f[1]}:${f[0]}'`;
			}
			return column_name;
		});
		const cdt_name_fields = this.get_unique_cdt_in_view().map(
			(cdt) => jingrow.model.get_full_column_name("name", cdt) + " as " + `'${cdt}:name'`
		);
		fields = fields.concat(cdt_name_fields);

		return fields;
	}

	get_unique_cdt_in_view() {
		return this.fields
			.filter((f) => f[1] !== this.pagetype)
			.map((f) => f[1])
			.uniqBy((d) => d);
	}

	add_column_to_datatable(fieldname, pagetype, col_index) {
		const field = [fieldname, pagetype];
		this.fields.splice(col_index, 0, field);

		this.add_currency_column(fieldname, pagetype, col_index);

		this.build_fields();
		this.setup_columns();

		if (this.datatable) this.datatable.destroy();
		this.datatable = null;
		this.refresh();
	}

	add_currency_column(fieldname, pagetype, col_index) {
		// Adds dependent currency field if required
		const df = jingrow.meta.get_pagefield(pagetype, fieldname);
		if (
			df &&
			df.fieldtype === "Currency" &&
			df.options &&
			!df.options.includes(":") &&
			jingrow.meta.has_field(pagetype, df.options)
		) {
			const field = [df.options, pagetype];
			if (col_index === undefined) {
				this.fields.push(field);
			} else {
				this.fields.splice(col_index, 0, field);
			}
			const field_label = jingrow.meta.get_label(pagetype, field[0]);
			jingrow.show_alert(
				__("Also adding the dependent currency field {0}", [__(field_label).bold()])
			);
		}
	}

	add_status_dependency_column(col, pagetype) {
		// Adds dependent column from which status is derived if required
		if (col && !this.fields.find((f) => f[0] === col)) {
			const field = [col, pagetype];
			this.fields.push(field);
			this.refresh();
			const field_label = jingrow.meta.get_label(pagetype, field[0]);
			jingrow.show_alert(
				__("Also adding the status dependency field {0}", [__(field_label).bold()])
			);
		}
	}

	remove_column_from_datatable(column) {
		const index = this.fields.findIndex((f) => column.field === f[0]);
		if (index === -1) return;
		const field = this.fields[index];

		if (field[0] === "name") {
			this.refresh();
			jingrow.throw(__("Cannot remove ID field"));
		}

		this.fields.splice(index, 1);
		this.build_fields();
		this.setup_columns();
		this.refresh();
	}

	switch_column(col1, col2) {
		const index1 = this.fields.findIndex((f) => col1.field === f[0]);
		const index2 = this.fields.findIndex((f) => col2.field === f[0]);
		const _fields = this.fields.slice();

		let temp = _fields[index1];
		_fields[index1] = _fields[index2];
		_fields[index2] = temp;

		this.fields = _fields;
		this.build_fields();
		this.setup_columns();
		this.refresh();
	}

	get_columns_for_picker() {
		let out = {};

		const standard_fields_filter = (df) => !jingrow.model.no_value_type.includes(df.fieldtype);

		let pagetype_fields = jingrow.meta
			.get_pagefields(this.pagetype)
			.filter(standard_fields_filter);

		// filter out pagestatus field from picker
		let std_fields = jingrow.model.std_fields.filter(
			(df) => !["pagestatus", "_comments"].includes(df.fieldname)
		);

		// add status field derived from pagestatus, if status is not a standard field
		let has_status_values = false;

		if (this.data) {
			has_status_values = jingrow.get_indicator(this.data[0], this.pagetype);
		}

		if (!jingrow.meta.has_field(this.pagetype, "status") && has_status_values) {
			pagetype_fields = [
				{
					label: __("Status"),
					fieldname: "pagestatus",
					fieldtype: "Data",
				},
			].concat(pagetype_fields);
		}

		pagetype_fields = [
			{
				label: __("ID", null, "Label of name column in report"),
				fieldname: "name",
				fieldtype: "Data",
				reqd: 1,
			},
		].concat(pagetype_fields, std_fields);

		out[this.pagetype] = pagetype_fields;

		const table_fields = jingrow.meta.get_table_fields(this.pagetype);

		table_fields.forEach((df) => {
			const cdt = df.options;
			const child_table_fields = jingrow.meta
				.get_pagefields(cdt)
				.filter(standard_fields_filter);

			out[cdt] = child_table_fields;

			// add index column for child tables
			out[cdt].push({
				label: __("Index"),
				fieldname: "idx",
				fieldtype: "Int",
				parent: cdt,
			});
		});
		return out;
	}

	get_dialog_fields() {
		const dialog_fields = [];
		const columns = this.get_columns_for_picker();

		dialog_fields.push({
			label: __(this.pagetype),
			fieldname: this.pagetype,
			fieldtype: "MultiCheck",
			columns: 2,
			options: columns[this.pagetype]
				.filter((df) => {
					return !df.hidden && df.fieldname !== "name";
				})
				.map((df) => ({
					label: __(df.label, null, df.parent),
					value: df.fieldname,
					checked: this.fields.find(
						(f) => f[0] === df.fieldname && f[1] === this.pagetype
					),
				})),
		});

		delete columns[this.pagetype];

		const table_fields = jingrow.meta.get_table_fields(this.pagetype).filter((df) => !df.hidden);

		table_fields.forEach((df) => {
			const cdt = df.options;

			dialog_fields.push({
				label: __(df.label, null, df.parent) + ` (${__(cdt)})`,
				fieldname: df.options,
				fieldtype: "MultiCheck",
				columns: 2,
				options: columns[cdt]
					.filter((df) => {
						return !df.hidden;
					})
					.map((df) => ({
						label: __(df.label, null, df.parent),
						value: df.fieldname,
						checked: this.fields.find((f) => f[0] === df.fieldname && f[1] === cdt),
					})),
			});
		});

		return dialog_fields;
	}

	is_column_added(df) {
		return Boolean(this.fields.find((f) => f[0] === df.fieldname && df.parent === f[1]));
	}

	setup_columns() {
		// apply previous column width
		let column_widths = null;
		if (this.columns) {
			column_widths = this.get_column_widths();
		}

		this.columns = [];
		this.columns_map = {};

		for (let f of this.fields) {
			let column;
			if (f[0] !== "pagestatus") {
				column = this.build_column(f);
			} else {
				// if status is not in fields append status column derived from pagestatus
				if (
					!this.fields.includes(["status", this.pagetype]) &&
					!jingrow.meta.has_field(this.pagetype, "status")
				) {
					column = this.build_column(["pagestatus", this.pagetype]);
				}
			}

			if (column) {
				if (column_widths) {
					column.width = column_widths[column.id] || column.width || 120;
				}
				this.columns.push(column);
				this.columns_map[column.id] = column;
			}
		}
	}

	build_column(c) {
		let [fieldname, pagetype] = c;
		let pagefield = jingrow.meta.pagefield_map[pagetype || this.pagetype][fieldname];

		// group by column
		if (fieldname === "_aggregate_column") {
			pagefield = this.group_by_control.get_group_by_pagefield();
		}

		// child table index column
		if (fieldname === "idx" && pagetype !== this.pagetype) {
			pagefield = {
				label: "Index",
				fieldtype: "Int",
				parent: pagetype,
			};
		}

		if (!pagefield) {
			pagefield = jingrow.model.get_std_field(fieldname, true);

			if (pagefield) {
				if (!pagefield.label) {
					pagefield.label = toTitle(fieldname);
					if (pagefield.label.includes("_")) {
						pagefield.label = pagefield.label.replace("_", " ");
					}
				}
				pagefield.parent = this.pagetype;
				if (fieldname == "name") {
					pagefield.options = this.pagetype;
				}
				if (fieldname == "pagestatus" && !jingrow.meta.has_field(this.pagetype, "status")) {
					pagefield.label = "Status";
					pagefield.fieldtype = "Data";
					pagefield.name = "status";
				}
			}
		}
		if (!pagefield || pagefield.report_hide) return;

		let title = __(pagefield.label, null, pagefield.parent);
		if (pagetype !== this.pagetype) {
			title += ` (${__(pagetype)})`;
		}

		const editable =
			jingrow.model.is_non_std_field(fieldname) &&
			!pagefield.read_only &&
			!pagefield.is_virtual;

		const align = (() => {
			const is_numeric = jingrow.model.is_numeric_field(pagefield);
			if (is_numeric) {
				return "right";
			}
			return pagefield.fieldtype === "Date" ? "right" : "left";
		})();

		let id = fieldname;

		// child table column
		if (pagetype !== this.pagetype && fieldname !== "_aggregate_column") {
			id = `${pagetype}:${fieldname}`;
		}

		let width = (pagefield ? cint(pagefield.width) : null) || null;
		if (this.report_pg) {
			// load the user saved column width
			let saved_column_widths = this.report_pg.json.column_widths || {};
			width = saved_column_widths[id] || width;
		}

		let compareFn = null;
		if (pagefield.fieldtype === "Date") {
			compareFn = (cell, keyword) => {
				if (!cell.content) return null;
				if (keyword.length !== "YYYY-MM-DD".length) return null;

				const keywordValue = jingrow.datetime.user_to_obj(keyword);
				const cellValue = jingrow.datetime.str_to_obj(cell.content);
				return [+cellValue, +keywordValue];
			};
		}

		return {
			id: id,
			field: fieldname,
			name: title,
			content: title,
			pagefield,
			width,
			editable,
			align,
			compareValue: compareFn,
			format: (value, row, column, data) => {
				let pg = null;
				if (Array.isArray(row)) {
					pg = row.reduce((acc, curr) => {
						if (!curr.column.pagefield) return acc;
						acc[curr.column.pagefield.fieldname] = curr.content;
						return acc;
					}, {});
				} else {
					pg = row;
				}

				return jingrow.format(value, column.pagefield, { always_show_decimals: true }, pg);
			},
		};
	}

	build_rows(data) {
		const out = data.map((d) => this.build_row(d));

		if (this.add_totals_row) {
			const totals = this.get_columns_totals(data);
			const totals_row = this.columns.map((col, i) => {
				return {
					name: __("Totals Row"),
					content: totals[col.id],
					format: (value) => {
						let formatted_value = jingrow.format(
							value,
							col.pagefield,
							{
								always_show_decimals: true,
							},
							data[0]
						);
						if (i === 0) {
							return this.format_total_cell(formatted_value, col);
						}
						return formatted_value;
					},
				};
			});

			out.push(totals_row);
		}
		return out;
	}

	format_total_cell(formatted_value, df) {
		let cell_value = __("Totals").bold();
		if (jingrow.model.is_numeric_field(df.pagefield)) {
			cell_value = `<span class="flex justify-between">
				${cell_value} ${$(formatted_value).text()}
			</span>`;
		}
		return cell_value;
	}

	build_row(d) {
		return this.columns.map((col) => {
			if (col.pagefield.parent !== this.pagetype) {
				// child table field
				const cdt_field = (f) => `${col.pagefield.parent}:${f}`;
				const name = d[cdt_field("name")];
				return {
					name: name,
					pagetype: col.pagefield.parent,
					content: d[cdt_field(col.field)] || d[col.field],
					editable: Boolean(name && this.is_editable(col.pagefield, d)),
					format: (value) => {
						return jingrow.format(
							value,
							col.pagefield,
							{ always_show_decimals: true },
							d
						);
					},
				};
			}
			if (col.field === "pagestatus" && !jingrow.meta.has_field(this.pagetype, "status")) {
				// get status from pagestatus
				let status = jingrow.get_indicator(d, this.pagetype);
				if (status) {
					// get_indicator returns the dependent field's condition as the 3rd parameter
					let dependent_col = status[2]?.split(",")[0];
					// add status dependency column
					this.add_status_dependency_column(dependent_col, this.pagetype);
					return {
						name: d.name,
						pagetype: col.pagefield.parent,
						content: status[0],
						editable: false,
					};
				} else {
					// no status values found
					this.remove_column_from_datatable(col);
				}
			} else if (col.field in d) {
				const value = d[col.field];
				return {
					name: d.name,
					pagetype: col.pagefield.parent,
					content: value,
					editable: this.is_editable(col.pagefield, d),
				};
			}
			return {
				content: "",
			};
		});
	}

	get_checked_items(only_pagenames) {
		const indexes = this.datatable.rowmanager.getCheckedRows();
		const items = indexes.map((i) => this.data[i]).filter((i) => i != undefined);

		if (only_pagenames) {
			return items.map((d) => d.name);
		}

		return items;
	}

	clear_checked_items() {
		this.datatable.rowmanager.checkAll(false);
	}

	save_report(save_type) {
		const _save_report = (name) => {
			// callback
			const report_settings = {
				filters: this.filter_area.get(),
				fields: this.fields,
				order_by: this.sort_selector.get_sql_string(),
				add_totals_row: this.add_totals_row,
				page_length: this.page_length,
				column_widths: this.get_column_widths(),
				group_by: this.group_by_control.get_settings(),
				chart_args: this.get_chart_settings(),
			};

			return jingrow.call({
				method: "jingrow.desk.reportview.save_report",
				args: {
					name: name,
					pagetype: this.pagetype,
					report_settings: JSON.stringify(report_settings),
				},
				callback: (r) => {
					if (r.exc) {
						jingrow.msgprint(__("Report was not saved (there were errors)"));
						return;
					}
					if (r.message != this.report_name) {
						// Rerender the reports dropdown,
						// so that this report is included in the dropdown as well.
						jingrow.boot.user.all_reports[r.message] = {
							ref_pagetype: this.pagetype,
							report_type: "Report Builder",
							title: r.message,
						};

						jingrow.set_route("List", this.pagetype, "Report", r.message);
						return;
					}

					// update state
					this.report_pg.json = report_settings;
					this.set_dirty_state_for_custom_report();
				},
			});
		};

		if (this.report_name && save_type == "save") {
			_save_report(this.report_name);
		} else {
			jingrow.prompt(
				{ fieldname: "name", label: __("New Report name"), reqd: 1, fieldtype: "Data" },
				(data) => {
					_save_report(data.name);
				},
				__("Save As")
			);
		}
	}

	delete_report() {
		return jingrow.call({
			method: "jingrow.desk.reportview.delete_report",
			args: { name: this.report_name },
			callback(response) {
				if (response.exc) return;
				window.history.back();
			},
		});
	}

	get_column_widths() {
		if (this.datatable) {
			return this.datatable.datamanager.getColumns(true).reduce((acc, curr) => {
				acc[curr.id] = parseInt(curr.width);
				return acc;
			}, {});
		}

		return {};
	}

	get_report_pg() {
		return new Promise((resolve) => {
			jingrow.model.with_pg("Report", this.report_name, () => {
				resolve(jingrow.get_pg("Report", this.report_name));
			});
		});
	}

	get_filters_html_for_print() {
		const filters = this.filter_area.get();

		return (
			`<h5>${__("Filters:")}</h5>` +
			filters
				.map((f) => {
					const [pagetype, fieldname, condition, value] = f;
					const pagefield = jingrow.meta.get_pagefield(pagetype, fieldname);
					const label = `<b>${__(jingrow.meta.get_label(pagetype, fieldname))}</b>`;
					switch (condition) {
						case "=":
							return __("{0} is equal to {1}", [
								label,
								jingrow.format(value, pagefield),
							]);
						case "!=":
							return __("{0} is not equal to {1}", [
								__(label),
								jingrow.format(value, pagefield),
							]);
						case ">":
							return __("{0} is greater than {1}", [
								__(label),
								jingrow.format(value, pagefield),
							]);
						case "<":
							return __("{0} is less than {1}", [
								__(label),
								jingrow.format(value, pagefield),
							]);
						case ">=":
							return __("{0} is greater than or equal to {1}", [
								__(label),
								jingrow.format(value, pagefield),
							]);
						case "<=":
							return __("{0} is less than or equal to {1}", [
								__(label),
								jingrow.format(value, pagefield),
							]);
						case "Between":
							return __("{0} is between {1} and {2}", [
								__(label),
								jingrow.format(value[0], pagefield),
								jingrow.format(value[1], pagefield),
							]);
						case "Timespan":
							return __("{0} is within {1}", [__(label), __(value)]);
						case "in":
							return __("{0} is one of {1}", [
								__(label),
								jingrow.utils.comma_or(
									value.map((v) => jingrow.format(v, pagefield))
								),
							]);
						case "not in":
							return __("{0} is not one of {1}", [
								__(label),
								jingrow.utils.comma_or(
									value.map((v) => jingrow.format(v, pagefield))
								),
							]);
						case "like":
							return __("{0} is like {1}", [__(label), value]);
						case "not like":
							return __("{0} is not like {1}", [__(label), value]);
						case "is":
							return value === "set"
								? __("{0} is set", [__(label)])
								: __("{0} is not set", [__(label)]);
						default:
							return null;
					}
				})
				.filter(Boolean)
				.join("<br>")
		);
	}

	get_columns_totals(data) {
		if (!this.add_totals_row) {
			return [];
		}

		const row_totals = {};

		this.columns.forEach((col, i) => {
			const totals = data.reduce((totals, d) => {
				if (col.id in d && jingrow.model.is_numeric_field(col.pagefield)) {
					totals += flt(d[col.id]);
					return totals;
				}
			}, 0);

			row_totals[col.id] = totals;
		});

		return row_totals;
	}

	report_menu_items() {
		let items = [
			{
				label: __("Show Totals"),
				action: () => {
					this.add_totals_row = !this.add_totals_row;
					this.save_view_user_settings({
						add_totals_row: this.add_totals_row,
					});
					this.datatable.refresh(this.get_data(this.data));
				},
			},
			{
				label: __("Print"),
				action: () => {
					// prepare rows in their current state, sorted and filtered
					const rows_in_order = this.datatable.datamanager.rowViewOrder
						.map((index) => {
							if (this.datatable.bodyRenderer.visibleRowIndices.includes(index)) {
								return this.data[index];
							}
						})
						.filter(Boolean);

					if (this.add_totals_row) {
						const total_data = this.get_columns_totals(this.data);

						total_data["name"] = __("Total");
						total_data.is_total_row = true;
						rows_in_order.push(total_data);
					}

					jingrow.ui.get_print_settings(false, (print_settings) => {
						var title = this.report_name || __(this.pagetype);
						jingrow.render_grid({
							title: title,
							subtitle: this.get_filters_html_for_print(),
							print_settings: print_settings,
							columns: this.columns,
							data: rows_in_order,
							can_use_smaller_font: 1,
						});
					});
				},
			},
			{
				label: __("Toggle Chart"),
				action: () => this.toggle_charts(),
			},
			{
				label: __("Toggle Sidebar"),
				action: () => this.toggle_side_bar(),
				shortcut: "Ctrl+K",
			},
			{
				label: __("Pick Columns"),
				action: () => {
					const d = new jingrow.ui.Dialog({
						title: __("Pick Columns"),
						fields: this.get_dialog_fields(),
						primary_action: (values) => {
							// pagetype fields
							let fields = values[this.pagetype].map((f) => [f, this.pagetype]);
							delete values[this.pagetype];

							// child table fields
							for (let cdt in values) {
								fields = fields.concat(values[cdt].map((f) => [f, cdt]));
							}

							// always keep name (ID) column
							this.fields = [["name", this.pagetype], ...fields];

							this.fields.map((f) => this.add_currency_column(f[0], f[1]));

							this.reorder_fields();
							this.build_fields();
							this.setup_columns();

							this.datatable.destroy();
							this.datatable = null;
							this.refresh();

							d.hide();
						},
					});

					d.$body.prepend(`
						<div class="columns-search">
							<input type="text" placeholder="${__(
								"Search"
							)}" data-element="search" class="form-control input-xs">
						</div>
					`);

					jingrow.utils.setup_search(d.$body, ".unit-checkbox", ".label-area");
					d.show();
				},
			},
		];

		if (jingrow.model.can_export(this.pagetype)) {
			items.push({
				label: __("Export"),
				action: () => {
					const args = this.get_args();
					const selected_items = this.get_checked_items(true);

					let extra_fields = [];
					if (this.list_view_settings.disable_count) {
						extra_fields = [
							{
								fieldtype: "Check",
								fieldname: "export_all_rows",
								label: __("Export all matching rows?"),
							},
						];
					} else if (
						this.total_count > (this.count_without_children || args.page_length)
					) {
						extra_fields = [
							{
								fieldtype: "Check",
								fieldname: "export_all_rows",
								label: __("Export all {0} rows?", [`<b>${this.total_count}</b>`]),
							},
						];
					}
					if (jingrow.boot.lang !== "en") {
						extra_fields.push({
							fieldtype: "Check",
							fieldname: "translate_values",
							label: __("Translate values"),
							default: 1,
						});
					}

					const d = jingrow.report_utils.get_export_dialog(
						__(this.pagetype),
						extra_fields,
						(data) => {
							args.cmd = "jingrow.desk.reportview.export_query";
							args.file_format_type = data.file_format;
							args.title = this.report_name || this.pagetype;
							args.translate_values = data.translate_values;

							if (data.file_format == "CSV") {
								args.csv_delimiter = data.csv_delimiter;
								args.csv_quoting = data.csv_quoting;
							}

							if (this.add_totals_row) {
								args.add_totals_row = 1;
							}

							if (selected_items.length > 0) {
								args.selected_items = selected_items;
							}

							if (!data.export_all_rows) {
								args.start = 0;
								args.page_length = this.data.length;
							} else {
								delete args.start;
								delete args.page_length;
							}

							open_url_post(jingrow.request.url, args);

							d.hide();
						}
					);

					d.show();
				},
			});
		}

		items.push({
			label: __("Setup Auto Email"),
			action: () => {
				if (this.report_name) {
					jingrow.set_route("List", "Auto Email Report", { report: this.report_name });
				} else {
					jingrow.msgprint(__("Please save the report first"));
				}
			},
		});

		const can_edit_or_delete = (action) => {
			const method = action == "delete" ? "can_delete" : "can_write";
			return (
				this.report_pg &&
				this.report_pg.is_standard !== "Yes" &&
				(jingrow.model[method]("Report") || this.report_pg.owner === jingrow.session.user)
			);
		};

		// A user with role Report Manager or Report Owner can save
		if (can_edit_or_delete()) {
			items.push({
				label: __("Save"),
				action: () => this.save_report("save"),
			});
		}

		// anyone can save as
		items.push({
			label: __("Save As"),
			action: () => this.save_report("save_as"),
		});

		// A user with role Report Manager or Report Owner can delete
		if (can_edit_or_delete("delete")) {
			items.push({
				label: __("Delete"),
				action: () =>
					jingrow.confirm("Are you sure you want to delete this report?", () =>
						this.delete_report()
					),
				shortcut: "Shift+Ctrl+D",
			});
		}

		// user permissions
		if (this.report_name && jingrow.user.has_role("System Manager")) {
			items.push({
				label: __("User Permissions"),
				action: () => {
					const args = {
						pagetype: "Report",
						name: this.report_name,
					};
					jingrow.set_route("List", "User Permission", args);
				},
			});
		}

		if (jingrow.user.has_role("System Manager")) {
			if (this.get_view_settings) {
				items.push(this.get_view_settings());
			}
		}

		return items.map((i) => Object.assign(i, { standard: true }));
	}

	get_search_params() {
		let search_params = super.get_search_params();
		let config = this.group_by_control.get_settings();
		if (config) {
			search_params.append(
				"_group_by",
				JSON.stringify([config.group_by, config.aggregate_on, config.aggregate_function])
			);
		}
		return search_params;
	}

	parse_filters_from_route_options() {
		if (jingrow.route_options?._group_by) {
			try {
				let config = JSON.parse(jingrow.route_options._group_by);
				this.group_by_control.apply_settings({
					group_by: config[0],
					aggregate_on: config[1],
					aggregate_function: config[2],
				});
				delete jingrow.route_options["_group_by"];
			} catch (e) {
				console.warn("Failed to parse group by from URL", e);
			}
		}

		return super.parse_filters_from_route_options();
	}
};
