// Copyright (c) 2018, InfluxFramework LLC
// MIT License. See license.txt
import DataTable from "influxframework-datatable";

influxframework.provide("influxframework.widget.utils");
influxframework.provide("influxframework.views");
influxframework.provide("influxframework.query_reports");

influxframework.standard_pages["query-report"] = function () {
	var wrapper = influxframework.container.add_page("query-report");

	influxframework.ui.make_app_page({
		parent: wrapper,
		title: __("Query Report"),
		single_column: true,
	});

	influxframework.query_report = new influxframework.views.QueryReport({
		parent: wrapper,
	});

	$(wrapper).bind("show", function () {
		influxframework.query_report.show();
	});
};

influxframework.views.QueryReport = class QueryReport extends influxframework.views.BaseList {
	show() {
		this.init().then(() => this.load());
	}

	init() {
		if (this.init_promise) {
			return this.init_promise;
		}

		let tasks = [
			this.setup_defaults,
			this.setup_page,
			this.setup_report_wrapper,
			this.setup_events,
		].map((fn) => fn.bind(this));
		this.init_promise = influxframework.run_serially(tasks);
		return this.init_promise;
	}

	setup_defaults() {
		this.route = influxframework.get_route();
		this.page_name = influxframework.get_route_str();

		// Setup buttons
		this.primary_action = null;

		// throttle refresh for 300ms
		this.refresh = influxframework.utils.throttle(this.refresh, 300);

		this.menu_items = [];
	}

	update_url_with_filters() {
		if (influxframework.get_route_str() == this.page_name) {
			window.history.replaceState(null, null, this.get_url_with_filters());
		}
	}

	get_url_with_filters() {
		const query_params = Object.entries(this.get_filter_values())
			.map(([field, value], _idx) => {
				// multiselects
				if (Array.isArray(value)) {
					if (!value.length) return "";
					value = JSON.stringify(value);
				}
				return `${field}=${encodeURIComponent(value)}`;
			})
			.filter(Boolean)
			.join("&");

		let full_url = window.location.href.replace(window.location.search, "");
		if (query_params) {
			full_url += "?" + query_params;
		}
		return full_url;
	}

	set_default_secondary_action() {
		this.refresh_button && this.refresh_button.remove();
		this.refresh_button = this.page.add_action_icon("refresh", () => {
			this.setup_progress_bar();
			this.refresh();
		});
	}

	get_no_result_message() {
		return `<div class="msg-box no-border">
			<div>
				<img src="/assets/influxframework/images/ui-states/list-empty-state.svg" alt="Generic Empty State" class="null-state">
			</div>
			<p>${__("Nothing to show")}</p>
		</div>`;
	}

	setup_events() {
		influxframework.realtime.on("report_generated", (data) => {
			this.toggle_primary_button_disabled(false);
			if (data.report_name) {
				this.prepared_report_action = "Rebuild";
				// If generated report and currently active Prepared Report has same fiters
				// then refresh the Prepared Report
				// Otherwise show alert with the link to the Prepared Report
				if (data.name == this.prepared_report_doc_name) {
					this.refresh();
				} else {
					let alert_message = `Report ${this.report_name} generated.
						<a href="#query-report/${this.report_name}/?prepared_report_name=${data.name}">View</a>`;
					influxframework.show_alert({ message: alert_message, indicator: "orange" });
				}
			}
		});
		this.page.wrapper.on("click", "[data-action]", (e) => {
			let action_name = $(e.currentTarget).data("action");
			let action = this[action_name];
			if (action.call) {
				action.call(this, e);
			}
		});
	}

	load() {
		if (influxframework.get_route().length < 2) {
			this.toggle_nothing_to_show(true);
			return;
		}

		let route_options = {};
		route_options = Object.assign(route_options, influxframework.route_options);

		if (this.report_name !== influxframework.get_route()[1]) {
			// different report
			this.load_report(route_options);
		} else if (influxframework.has_route_options()) {
			// filters passed through routes
			// so refresh report again
			this.refresh_report(route_options);
		} else {
			// same report
			// don't do anything to preserve state
			// like filters and datatable column widths
		}
	}

	load_report(route_options) {
		this.page.clear_inner_toolbar();
		this.route = influxframework.get_route();
		this.page_name = influxframework.get_route_str();
		this.report_name = this.route[1];
		this.page_title = __(this.report_name);
		this.show_save = false;
		this.menu_items = this.get_menu_items();
		this.datatable = null;
		this.prepared_report_action = "New";

		influxframework.run_serially([
			() => this.get_report_doc(),
			() => this.get_report_settings(),
			() => this.setup_progress_bar(),
			() => this.setup_page_head(),
			() => this.refresh_report(route_options),
			() => this.add_chart_buttons_to_toolbar(true),
			() => this.add_card_button_to_toolbar(true),
		]);
	}

	add_card_button_to_toolbar() {
		this.page.add_inner_button(__("Create Card"), () => {
			this.add_card_to_dashboard();
		});
	}

	add_chart_buttons_to_toolbar(show) {
		if (show) {
			this.create_chart_button && this.create_chart_button.remove();
			this.create_chart_button = this.page.add_button(__("Set Chart"), () => {
				this.open_create_chart_dialog();
			});

			if (this.chart_fields || this.chart_options) {
				this.add_to_dashboard_button && this.add_to_dashboard_button.remove();
				this.add_to_dashboard_button = this.page.add_button(
					__("Add Chart to Dashboard"),
					() => {
						this.add_chart_to_dashboard();
					}
				);
			}
		} else {
			this.create_chart_button && this.create_chart_button.remove();
			this.add_to_dashboard_button && this.add_to_dashboard_button.remove();
		}
	}

	add_card_to_dashboard() {
		let field_options = influxframework.report_utils.get_field_options_from_report(
			this.columns,
			this.raw_data
		);
		const dashboard_field = influxframework.dashboard_utils.get_dashboard_link_field();
		const set_standard = influxframework.boot.developer_mode;

		const dialog = new influxframework.ui.Dialog({
			title: __("Create Card"),
			fields: [
				{
					fieldname: "report_field",
					label: __("Field"),
					fieldtype: "Select",
					options: field_options.numeric_fields,
				},
				{
					fieldname: "cb_1",
					fieldtype: "Column Break",
				},
				{
					fieldname: "report_function",
					label: __("Function"),
					options: ["Sum", "Average", "Minimum", "Maximum"],
					fieldtype: "Select",
				},
				{
					fieldname: "sb_1",
					label: __("Add to Dashboard"),
					fieldtype: "Section Break",
				},
				dashboard_field,
				{
					fieldname: "cb_2",
					fieldtype: "Column Break",
				},
				{
					fieldname: "label",
					label: __("Card Label"),
					fieldtype: "Data",
				},
			],
			primary_action_label: __("Add"),
			primary_action: (values) => {
				if (!values.label) {
					values.label = `${values.report_function} of ${toTitle(values.report_field)}`;
				}
				this.create_number_card(values, values.dashboard, values.label, set_standard);
				dialog.hide();
			},
		});

		dialog.show();
	}

	add_chart_to_dashboard() {
		if (this.chart_fields || this.chart_options) {
			const dashboard_field = influxframework.dashboard_utils.get_dashboard_link_field();
			const set_standard = influxframework.boot.developer_mode;

			const dialog = new influxframework.ui.Dialog({
				title: __("Create Chart"),
				fields: [
					{
						fieldname: "dashboard_chart_name",
						label: "Chart Name",
						fieldtype: "Data",
					},
					dashboard_field,
				],
				primary_action_label: __("Add"),
				primary_action: (values) => {
					this.create_dashboard_chart(
						this.chart_fields || this.chart_options,
						values.dashboard,
						values.dashboard_chart_name,
						set_standard
					);
					dialog.hide();
				},
			});

			dialog.show();
		} else {
			influxframework.msgprint(__("Please Set Chart"));
		}
	}

	create_number_card(values, dashboard_name, card_name, set_standard) {
		let args = {
			dashboard: dashboard_name || null,
			type: "Report",
			report_name: this.report_name,
			filters_json: JSON.stringify(this.get_filter_values()),
			set_standard: set_standard,
		};
		Object.assign(args, values);

		this.add_to_dashboard(
			"influxframework.desk.doctype.number_card.number_card.create_report_number_card",
			args,
			dashboard_name,
			card_name,
			"Number Card"
		);
	}

	create_dashboard_chart(chart_args, dashboard_name, chart_name, set_standard) {
		let args = {
			dashboard: dashboard_name || null,
			chart_type: "Report",
			report_name: this.report_name,
			type: chart_args.chart_type || influxframework.model.unscrub(chart_args.type),
			color: chart_args.color,
			filters_json: JSON.stringify(this.get_filter_values()),
			custom_options: {},
			set_standard: set_standard,
		};

		for (let key in chart_args) {
			if (key != "data") {
				args["custom_options"][key] = chart_args[key];
			}
		}

		if (this.chart_fields) {
			let x_field_title = toTitle(chart_args.x_field);
			let y_field_title = toTitle(chart_args.y_fields[0]);
			chart_name = chart_name || `${this.report_name}: ${x_field_title} vs ${y_field_title}`;

			Object.assign(args, {
				chart_name: chart_name,
				x_field: chart_args.x_field,
				y_axis: chart_args.y_axis_fields.map((f) => {
					return { y_field: f.y_field, color: f.color };
				}),
				use_report_chart: 0,
			});
		} else {
			chart_name = chart_name || this.report_name;
			Object.assign(args, {
				chart_name: chart_name,
				use_report_chart: 1,
			});
		}

		this.add_to_dashboard(
			"influxframework.desk.doctype.dashboard_chart.dashboard_chart.create_report_chart",
			args,
			dashboard_name,
			chart_name,
			"Dashboard Chart"
		);
	}

	add_to_dashboard(method, args, dashboard_name, name, doctype) {
		influxframework.xcall(method, { args: args }).then(() => {
			let message;
			if (dashboard_name) {
				let dashboard_route_html = `<a href="#dashboard-view/${dashboard_name}">${dashboard_name}</a>`;
				message = __("New {0} {1} added to Dashboard {2}", [
					__(doctype),
					name,
					dashboard_route_html,
				]);
			} else {
				message = __("New {0} {1} created", [__(doctype), name]);
			}

			influxframework.msgprint(message, __("New {0} Created", [__(doctype)]));
		});
	}

	refresh_report(route_options) {
		this.toggle_message(true);
		this.toggle_report(false);

		return influxframework.run_serially([
			() => this.setup_filters(),
			() => this.set_route_filters(route_options),
			() => this.page.clear_custom_actions(),
			() => this.report_settings.onload && this.report_settings.onload(this),
			() => this.refresh(),
		]);
	}

	get_report_doc() {
		return influxframework.model
			.with_doc("Report", this.report_name)
			.then((doc) => {
				this.report_doc = doc;
			})
			.then(() => influxframework.model.with_doctype(this.report_doc.ref_doctype));
	}

	get_report_settings() {
		return new Promise((resolve, reject) => {
			if (influxframework.query_reports[this.report_name]) {
				this.report_settings = influxframework.query_reports[this.report_name];
				resolve();
			} else {
				influxframework
					.xcall("influxframework.desk.query_report.get_script", {
						report_name: this.report_name,
					})
					.then((settings) => {
						influxframework.dom.eval(settings.script || "");
						influxframework.after_ajax(() => {
							this.report_settings = this.get_local_report_settings();
							this.report_settings.html_format = settings.html_format;
							this.report_settings.execution_time = settings.execution_time || 0;
							influxframework.query_reports[this.report_name] = this.report_settings;

							if (this.report_doc.filters && !this.report_settings.filters) {
								// add configured filters
								this.report_settings.filters = this.report_doc.filters;
							}

							resolve();
						});
					})
					.catch(reject);
			}
		});
	}

	get_local_report_settings() {
		let report_script_name =
			this.report_doc.report_type === "Custom Report"
				? this.report_doc.reference_report
				: this.report_name;
		return influxframework.query_reports[report_script_name] || {};
	}

	setup_progress_bar() {
		let seconds_elapsed = 0;
		const execution_time = this.report_settings.execution_time || 0;

		if (execution_time < 5) return;

		this.interval = setInterval(function () {
			seconds_elapsed += 1;
			influxframework.show_progress(__("Preparing Report"), seconds_elapsed, execution_time);
		}, 1000);
	}

	refresh_filters_dependency() {
		this.filters.forEach((filter) => {
			filter.guardian_has_value = true;

			if (filter.df.depends_on) {
				filter.guardian_has_value = this.evaluate_depends_on_value(
					filter.df.depends_on,
					filter.df.label
				);

				if (filter.guardian_has_value) {
					if (filter.df.hidden_due_to_dependency) {
						filter.df.hidden_due_to_dependency = false;
						this.toggle_filter_display(filter.df.fieldname, false);
					}
				} else {
					if (!filter.df.hidden_due_to_dependency) {
						filter.df.hidden_due_to_dependency = true;
						this.toggle_filter_display(filter.df.fieldname, true);
						filter.set_value(filter.df.default || null);
					}
				}
			}
		});
	}

	evaluate_depends_on_value(expression, filter_label) {
		let out = null;
		let doc = this.get_filter_values();
		if (doc) {
			if (typeof expression === "boolean") {
				out = expression;
			} else if (expression.substr(0, 5) == "eval:") {
				try {
					out = influxframework.utils.eval(expression.substr(5), { doc });
				} catch (e) {
					influxframework.throw(
						__('Invalid "depends_on" expression set in filter {0}', [filter_label])
					);
				}
			} else {
				var value = doc[expression];
				if ($.isArray(value)) {
					out = !!value.length;
				} else {
					out = !!value;
				}
			}
		}
		return out;
	}

	setup_filters() {
		this.clear_filters();
		const { filters = [] } = this.report_settings;

		let filter_area = this.page.page_form;

		this.filters = filters
			.map((df) => {
				if (df.fieldtype === "Break") return;

				let f = this.page.add_field(df, filter_area);

				if (df.default) {
					f.set_input(df.default);
				}

				if (df.get_query) f.get_query = df.get_query;
				if (df.on_change) f.on_change = df.on_change;

				df.onchange = () => {
					this.refresh_filters_dependency();

					let current_filters = this.get_filter_values();
					if (
						this.previous_filters &&
						JSON.stringify(this.previous_filters) === JSON.stringify(current_filters)
					) {
						// filter values have not changed
						return;
					}

					// clear previous_filters after 10 seconds, to allow refresh for new data
					this.previous_filters = current_filters;
					setTimeout(() => (this.previous_filters = null), 10000);

					if (f.on_change) {
						f.on_change(this);
					} else {
						if (this.prepared_report) {
							this.reset_report_view();
						} else if (!this._no_refresh) {
							this.refresh();
						}
					}
				};

				f = Object.assign(f, df);

				return f;
			})
			.filter(Boolean);

		this.refresh_filters_dependency();
		if (this.filters.length === 0) {
			// hide page form if no filters
			this.page.hide_form();
		} else {
			this.page.show_form();
		}
	}

	set_filters(filters) {
		this.filters.map((f) => {
			f.set_input(filters[f.fieldname]);
		});
	}

	set_route_filters(route_options) {
		if (!route_options) route_options = influxframework.route_options;

		if (route_options) {
			const fields = Object.keys(route_options);

			const filters_to_set = this.filters.filter((f) => fields.includes(f.df.fieldname));

			const promises = filters_to_set.map((f) => {
				return () => {
					let value = route_options[f.df.fieldname];
					if (typeof value === "string" && value[0] === "[") {
						// multiselect array
						value = JSON.parse(value);
					}
					f.set_value(value);
				};
			});
			promises.push(() => {
				influxframework.route_options = null;
			});

			return influxframework.run_serially(promises);
		}
	}

	clear_filters() {
		this.page.clear_fields();
	}

	refresh() {
		this.toggle_message(true);
		this.toggle_report(false);
		let filters = this.get_filter_values(true);
		this.show_loading_screen();

		// only one refresh at a time
		if (this.last_ajax) {
			this.last_ajax.abort();
		}

		const query_params = this.get_query_params();

		if (query_params.prepared_report_name) {
			filters.prepared_report_name = query_params.prepared_report_name;
		}

		return new Promise((resolve) => {
			this.last_ajax = influxframework.call({
				method: "influxframework.desk.query_report.run",
				type: "GET",
				args: {
					report_name: this.report_name,
					filters: filters,
					is_tree: this.report_settings.tree,
					parent_field: this.report_settings.parent_field,
				},
				callback: resolve,
				always: () => this.page.btn_secondary.prop("disabled", false),
			});
		})
			.then((r) => {
				let data = r.message;
				this.hide_status();
				clearInterval(this.interval);

				this.execution_time = data.execution_time || 0.1;

				if (data.prepared_report) {
					this.prepared_report = true;
					this.prepared_report_document = data.doc;
					// If query_string contains prepared_report_name then set filters
					// to match the mentioned prepared report doc and disable editing
					if (query_params.prepared_report_name) {
						this.prepared_report_action = "Edit";
						const filters_from_report = JSON.parse(data.doc.filters);
						Object.values(this.filters).forEach(function (field) {
							if (filters_from_report[field.fieldname]) {
								field.set_input(filters_from_report[field.fieldname]);
							}
							if (field.input) {
								field.input.disabled = true;
							}
						});
					}
					this.add_prepared_report_buttons(data.doc);
				}

				if (data.report_summary) {
					this.$summary.empty();
					this.render_summary(data.report_summary);
				}

				if (data.message && !data.prepared_report) this.show_status(data.message);

				this.toggle_message(false);
				if (data.result && data.result.length) {
					this.prepare_report_data(data);
					this.chart_options = this.get_chart_options(data);

					this.$chart.empty();
					if (this.chart_options) {
						this.render_chart(this.chart_options);
					} else {
						this.$chart.empty();
						if (this.chart_fields) {
							this.chart_options = influxframework.report_utils.make_chart_options(
								this.columns,
								this.raw_data,
								this.chart_fields
							);
							this.chart_options && this.render_chart(this.chart_options);
						}
					}
					this.render_datatable();
					this.add_chart_buttons_to_toolbar(true);
					this.add_card_button_to_toolbar();
					this.$report.show();
				} else {
					this.data = [];
					this.toggle_nothing_to_show(true);
					this.add_chart_buttons_to_toolbar(false);
				}

				this.show_footer_message();
				influxframework.hide_progress();
			})
			.finally(() => {
				this.hide_loading_screen();
				this.update_url_with_filters();
			});
	}

	render_summary(data) {
		data.forEach((summary) => {
			influxframework.utils.build_summary_item(summary).appendTo(this.$summary);
		});

		this.$summary.show();
	}

	get_query_params() {
		const query_string = influxframework.utils.get_query_string(influxframework.get_route_str());
		const query_params = influxframework.utils.get_query_params(query_string);
		return query_params;
	}

	add_prepared_report_buttons(doc) {
		if (doc) {
			this.page.add_inner_button(__("Download Report"), function () {
				window.open(
					influxframework.urllib.get_full_url(
						"/api/method/influxframework.core.doctype.prepared_report.prepared_report.download_attachment?" +
							"dn=" +
							encodeURIComponent(doc.name)
					)
				);
			});

			const part1 = __("This report was generated {0}.", [
				influxframework.datetime.comment_when(doc.report_end_time),
			]);
			const part2 = __("To get the updated report, click on {0}.", [__("Rebuild")]);
			const part3 = __("See all past reports.");

			this.show_status(`
				<div class="indicator orange">
					<span>
						${part1}
						${part2}
						<a href="/app/List/Prepared%20Report?report_name=${this.report_name}"> ${part3}</a>
					</span>
				</div>
			`);
		}

		// Three cases
		// 1. First time with given filters, no data.
		// 2. Showing data from specific report
		// 3. Showing data from an old report without specific report name
		this.primary_action_map = {
			New: {
				label: __("Generate New Report"),
				click: () => {
					this.show_warning_or_generate_report();
				},
			},
			Edit: {
				label: __("Edit"),
				click: () => {
					influxframework.set_route(influxframework.get_route());
				},
			},
			Rebuild: {
				label: __("Rebuild"),
				click: () => {
					this.show_warning_or_generate_report();
				},
			},
		};

		let primary_action = this.primary_action_map[this.prepared_report_action];

		if (!this.primary_button || this.primary_button.text() !== primary_action.label) {
			this.primary_button = this.page.set_primary_action(
				primary_action.label,
				primary_action.click
			);
		}
	}

	toggle_primary_button_disabled(disable) {
		this.primary_button.prop("disabled", disable);
	}

	show_warning_or_generate_report() {
		influxframework
			.xcall(
				"influxframework.core.doctype.prepared_report.prepared_report.get_reports_in_queued_state",
				{
					filters: this.get_filter_values(),
					report_name: this.report_name,
				}
			)
			.then((reports) => {
				this.queued_prepared_reports = reports;

				if (reports.length) {
					const message = this.get_queued_prepared_reports_warning_message(reports);
					this.prepared_report_dialog = influxframework.warn(
						__("Reports already in Queue"),
						message,
						() => this.generate_background_report(),
						__("Proceed Anyway"),
						true
					);

					this.prepared_report_dialog.footer.prepend(`
					<button type="button" class="btn btn-sm btn-default pull-left" data-action="delete_old_queued_reports">
						${__("Delete and Generate New")}
					</button>`);

					influxframework.utils.bind_actions_with_object(
						this.prepared_report_dialog.wrapper,
						this
					);
				} else {
					this.generate_background_report();
				}
			});
	}

	get_queued_prepared_reports_warning_message(reports) {
		const route = `/app/List/Prepared Report/List?status=Queued&report_name=${this.report_name}`;
		const report_link_html =
			reports.length == 1
				? `<a class="underline" href="${route}">${__("1 Report")}</a>`
				: `<a class="underline" href="${route}">${__("{0} Reports", [
						reports.length,
				  ])}</a>`;

		const no_of_reports_html =
			reports.length == 1
				? `${__("There is {0} with the same filters already in the queue:", [
						report_link_html,
				  ])}`
				: `${__("There are {0} with the same filters already in the queue:", [
						report_link_html,
				  ])}`;

		let warning_message = `
			<p>
				${__("Are you sure you want to generate a new report?")}
				${no_of_reports_html}
			</p>`;

		let get_item_html = (item) =>
			`<a class="underline" href="/app/prepared-report/${item.name}">${item.name}</a>`;

		warning_message += reports.map(get_item_html).join(", ");

		return warning_message;
	}

	delete_old_queued_reports() {
		this.prepared_report_dialog.hide();
		influxframework
			.xcall("influxframework.core.doctype.prepared_report.prepared_report.delete_prepared_reports", {
				reports: this.queued_prepared_reports,
			})
			.then(() => this.generate_background_report());
	}

	generate_background_report() {
		this.toggle_primary_button_disabled(true);
		let mandatory = this.filters.filter((f) => f.df.reqd);
		let missing_mandatory = mandatory.filter((f) => !f.get_value());
		if (!missing_mandatory.length) {
			let filters = this.get_filter_values(true);
			return new Promise((resolve) =>
				influxframework.call({
					method: "influxframework.desk.query_report.background_enqueue_run",
					type: "GET",
					args: {
						report_name: this.report_name,
						filters: filters,
					},
					callback: resolve,
				})
			).then((r) => {
				const data = r.message;
				// Rememeber the name of Prepared Report doc
				this.prepared_report_doc_name = data.name;
				let alert_message =
					`<a href='/app/prepared-report/${data.name}'>` +
					__("Report initiated, click to view status") +
					`</a>`;
				influxframework.show_alert({ message: alert_message, indicator: "orange" }, 10);
				this.toggle_nothing_to_show(true);
			});
		}
	}

	prepare_report_data(data) {
		this.raw_data = data;
		this.columns = this.prepare_columns(data.columns);
		this.custom_columns = [];
		this.data = this.prepare_data(data.result);
		this.linked_doctypes = this.get_linked_doctypes();
		this.tree_report = this.data.some((d) => "indent" in d);
	}

	render_datatable() {
		let data = this.data;
		let columns = this.columns.filter((col) => !col.hidden);

		if (this.raw_data.add_total_row && !this.report_settings.tree) {
			data = data.slice();
			data.splice(-1, 1);
		}

		this.$report.show();
		if (
			this.datatable &&
			this.datatable.options &&
			this.datatable.options.showTotalRow === this.raw_data.add_total_row
		) {
			this.datatable.options.treeView = this.tree_report;
			this.datatable.refresh(data, columns);
		} else {
			let datatable_options = {
				columns: columns,
				data: data,
				inlineFilters: true,
				language: influxframework.boot.lang,
				translations: influxframework.utils.datatable.get_translations(),
				treeView: this.tree_report,
				layout: "fixed",
				cellHeight: 33,
				showTotalRow: this.raw_data.add_total_row && !this.report_settings.tree,
				direction: influxframework.utils.is_rtl() ? "rtl" : "ltr",
				hooks: {
					columnTotal: influxframework.utils.report_column_total,
				},
			};

			if (this.report_settings.get_datatable_options) {
				datatable_options = this.report_settings.get_datatable_options(datatable_options);
			}
			this.datatable = new DataTable(this.$report[0], datatable_options);
		}

		if (typeof this.report_settings.initial_depth == "number") {
			this.datatable.rowmanager.setTreeDepth(this.report_settings.initial_depth);
		}
		if (this.report_settings.after_datatable_render) {
			this.report_settings.after_datatable_render(this.datatable);
		}
	}

	show_loading_screen() {
		const loading_state = `<div class="msg-box no-border">
			<div>
				<img src="/assets/influxframework/images/ui-states/list-empty-state.svg" alt="Generic Empty State" class="null-state">
			</div>
			<p>${__("Loading")}...</p>
		</div>`;

		this.$loading.find("div").html(loading_state);
		this.$report.hide();
		this.$loading.show();
	}

	hide_loading_screen() {
		this.$loading.hide();
	}

	get_chart_options(data) {
		let options = this.report_settings.get_chart_data
			? this.report_settings.get_chart_data(data.columns, data.result)
			: data.chart
			? data.chart
			: undefined;

		if (!(options && options.data && options.data.labels && options.data.labels.length > 0))
			return;

		if (options.fieldtype) {
			options.tooltipOptions = {
				formatTooltipY: (d) =>
					influxframework.format(d, {
						fieldtype: options.fieldtype,
						options: options.options,
					}),
			};
		}
		options.axisOptions = {
			shortenYAxisNumbers: 1,
			numberFormatter: influxframework.utils.format_chart_axis_number,
		};
		options.height = 280;
		return options;
	}

	render_chart(options) {
		this.$chart.empty();
		this.$chart.show();
		this.chart = new influxframework.Chart(this.$chart[0], options);
	}

	open_create_chart_dialog() {
		const me = this;
		let field_options = influxframework.report_utils.get_field_options_from_report(
			this.columns,
			this.raw_data
		);

		function set_chart_values(values) {
			values.y_fields = [];
			values.colors = [];
			if (values.y_axis_fields) {
				values.y_axis_fields.map((f) => {
					values.y_fields.push(f.y_field);
					values.colors.push(f.color);
				});
			}

			values.y_fields = values.y_fields.map((d) => d.trim()).filter(Boolean);

			return values;
		}

		function preview_chart() {
			const wrapper = $(dialog.fields_dict["chart_preview"].wrapper);
			let values = dialog.get_values(true);
			values = set_chart_values(values);

			if (values.x_field && values.y_fields.length) {
				let options = influxframework.report_utils.make_chart_options(
					me.columns,
					me.raw_data,
					values
				);
				me.chart_fields = values;
				wrapper.empty();
				new influxframework.Chart(wrapper[0], options);
				wrapper.find(".chart-container .title, .chart-container .sub-title").hide();
				wrapper.show();

				dialog.fields_dict["create_dashoard_chart"].df.hidden = 0;
				dialog.refresh();
			} else {
				wrapper[0].innerHTML = `<div class="flex justify-center align-center text-muted" style="height: 120px; display: flex;">
					<div>${__("Please select X and Y fields")}</div>
				</div>`;
			}
		}

		const dialog = new influxframework.ui.Dialog({
			title: __("Create Chart"),
			fields: [
				{
					fieldname: "x_field",
					label: "X Field",
					fieldtype: "Select",
					default: me.chart_fields ? me.chart_fields.x_field : null,
					options: field_options.non_numeric_fields,
				},
				{
					fieldname: "cb_1",
					fieldtype: "Column Break",
				},
				{
					fieldname: "chart_type",
					label: "Type of Chart",
					fieldtype: "Select",
					options: ["Bar", "Line", "Percentage", "Pie", "Donut"],
					default: me.chart_fields ? me.chart_fields.chart_type : "Bar",
				},
				{
					fieldname: "sb_1",
					fieldtype: "Section Break",
					label: "Y axis",
				},
				{
					fieldname: "y_axis_fields",
					fieldtype: "Table",
					fields: [
						{
							fieldtype: "Select",
							fieldname: "y_field",
							name: "y_field",
							label: __("Y Field"),
							options: field_options.numeric_fields,
							in_list_view: 1,
						},
						{
							fieldtype: "Color",
							fieldname: "color",
							name: "color",
							label: __("Color"),
							in_list_view: 1,
						},
					],
				},
				{
					fieldname: "preview_chart_button",
					fieldtype: "Button",
					label: "Preview Chart",
					click: preview_chart,
				},
				{
					fieldname: "sb_2",
					fieldtype: "Section Break",
					label: "Chart Preview",
				},
				{
					fieldname: "chart_preview",
					label: "Chart Preview",
					fieldtype: "HTML",
				},
				{
					fieldname: "create_dashoard_chart",
					label: "Add Chart to Dashboard",
					fieldtype: "Button",
					hidden: 1,
					click: () => {
						dialog.hide();
						this.add_chart_to_dashboard();
					},
				},
			],
			primary_action_label: __("Create"),
			primary_action: (values) => {
				values = set_chart_values(values);

				let options = influxframework.report_utils.make_chart_options(
					this.columns,
					this.raw_data,
					values
				);
				me.chart_fields = values;

				let x_field_label = field_options.numeric_fields.filter(
					(field) => field.value == values.y_fields[0]
				)[0].label;
				let y_field_label = field_options.non_numeric_fields.filter(
					(field) => field.value == values.x_field
				)[0].label;

				options.title = __("{0}: {1} vs {2}", [
					this.report_name,
					x_field_label,
					y_field_label,
				]);

				this.render_chart(options);
				this.add_chart_buttons_to_toolbar(true);

				dialog.hide();
			},
		});

		dialog.show();

		// load preview after dialog animation
		setTimeout(preview_chart, 500);
	}

	prepare_columns(columns) {
		return columns.map((column) => {
			column = influxframework.report_utils.prepare_field_from_column(column);

			const format_cell = (value, row, column, data) => {
				if (column.isHeader && !data && this.data) {
					// totalRow doesn't have a data object
					// proxy it using the first data object
					// applied to Float, Currency fields, needed only for currency formatting.
					// make first data column have value 'Total'
					let index = 1;

					if (this.report_settings.get_datatable_options) {
						let datatable = this.report_settings.get_datatable_options({});
						if (datatable && datatable.checkboxColumn) index = 2;
					}

					if (column.colIndex === index && !value) {
						value = "Total";
						column = { fieldtype: "Data" }; // avoid type issues for value if Date column
					} else if (in_list(["Currency", "Float"], column.fieldtype)) {
						// proxy for currency and float
						data = this.data[0];
					}
				}
				return influxframework.format(
					value,
					column,
					{ for_print: false, always_show_decimals: true },
					data
				);
			};

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

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

			return Object.assign(column, {
				id: column.fieldname,
				name: __(column.label, null, `Column of report '${this.report_name}'`), // context has to match context in   get_messages_from_report in translate.py
				width: parseInt(column.width) || null,
				editable: false,
				compareValue: compareFn,
				format: (value, row, column, data) => {
					if (this.report_settings.formatter) {
						return this.report_settings.formatter(
							value,
							row,
							column,
							data,
							format_cell
						);
					}
					return format_cell(value, row, column, data);
				},
			});
		});
	}

	prepare_data(data) {
		return data.map((row) => {
			let row_obj = {};
			if (Array.isArray(row)) {
				this.columns.forEach((column, i) => {
					row_obj[column.id] = row[i];
				});

				return row_obj;
			}
			return row;
		});
	}

	get_visible_columns() {
		const visible_column_ids = this.datatable.datamanager
			.getColumns(true)
			.map((col) => col.id);

		return visible_column_ids
			.map((id) => this.columns.find((col) => col.id === id))
			.filter(Boolean);
	}

	get_filter_values(raise) {
		// check for mandatory property for filters added via UI
		const mandatory = this.filters.filter((f) => f.df.reqd || f.df.mandatory);
		const missing_mandatory = mandatory.filter((f) => !f.get_value());
		if (raise && missing_mandatory.length > 0) {
			let message = __("Please set filters");
			this.hide_loading_screen();
			this.toggle_message(raise, message);
			throw "Filter missing";
		}

		raise && this.toggle_message(false);

		const filters = this.filters
			.filter((f) => f.get_value())
			.map((f) => {
				var v = f.get_value();
				// hidden fields dont have $input
				if (f.df.hidden) v = f.value;
				if (v === "%") v = null;
				if (f.df.wildcard_filter) {
					v = `%${v}%`;
				}
				return {
					[f.df.fieldname]: v,
				};
			})
			.reduce((acc, f) => {
				Object.assign(acc, f);
				return acc;
			}, {});
		return filters;
	}

	get_filter(fieldname) {
		const field = (this.filters || []).find((f) => f.df.fieldname === fieldname);
		if (!field) {
			console.warn(`[Query Report] Invalid filter: ${fieldname}`);
		}
		return field;
	}

	get_filter_value(fieldname) {
		const field = this.get_filter(fieldname);
		return field ? field.get_value() : null;
	}

	set_filter_value(fieldname, value) {
		let field_value_map = {};
		if (typeof fieldname === "string") {
			field_value_map[fieldname] = value;
		} else {
			field_value_map = fieldname;
		}

		this._no_refresh = true;
		Object.keys(field_value_map).forEach((fieldname, i, arr) => {
			const value = field_value_map[fieldname];

			if (i === arr.length - 1) {
				this._no_refresh = false;
			}

			this.get_filter(fieldname).set_value(value);
		});
	}

	set_breadcrumbs() {
		if (!this.report_doc || !this.report_doc.ref_doctype) return;
		const ref_doctype = influxframework.get_meta(this.report_doc.ref_doctype);
		influxframework.breadcrumbs.add(ref_doctype.module);
	}

	make_access_log(method, file_format) {
		influxframework.call("influxframework.core.doctype.access_log.access_log.make_access_log", {
			doctype: this.doctype || "",
			report_name: this.report_name,
			filters: this.get_filter_values(),
			file_type: file_format,
			method: method,
		});
	}

	print_report(print_settings) {
		const custom_format = this.report_settings.html_format || null;
		const filters_html = this.get_filters_html_for_print();
		const landscape = print_settings.orientation == "Landscape";

		this.make_access_log("Print", "PDF");
		influxframework.render_grid({
			template: print_settings.columns ? "print_grid" : custom_format,
			title: __(this.report_name),
			subtitle: filters_html,
			print_settings: print_settings,
			landscape: landscape,
			filters: this.get_filter_values(),
			data: this.get_data_for_print(),
			columns: this.get_columns_for_print(print_settings, custom_format),
			original_data: this.data,
			report: this,
		});
	}

	pdf_report(print_settings) {
		const base_url = influxframework.urllib.get_base_url();
		const print_css = influxframework.boot.print_css;
		const landscape = print_settings.orientation == "Landscape";

		const custom_format = this.report_settings.html_format || null;
		const columns = this.get_columns_for_print(print_settings, custom_format);
		const data = this.get_data_for_print();
		const applied_filters = this.get_filter_values();

		const filters_html = this.get_filters_html_for_print();
		const template = print_settings.columns || !custom_format ? "print_grid" : custom_format;
		const content = influxframework.render_template(template, {
			title: __(this.report_name),
			subtitle: filters_html,
			filters: applied_filters,
			data: data,
			original_data: this.data,
			columns: columns,
			report: this,
		});

		// Render Report in HTML
		const html = influxframework.render_template("print_template", {
			title: __(this.report_name),
			content: content,
			base_url: base_url,
			print_css: print_css,
			print_settings: print_settings,
			landscape: landscape,
			columns: columns,
			lang: influxframework.boot.lang,
			layout_direction: influxframework.utils.is_rtl() ? "rtl" : "ltr",
		});

		let filter_values = [],
			name_len = 0;
		for (var key of Object.keys(applied_filters)) {
			name_len = name_len + applied_filters[key].toString().length;
			if (name_len > 200) break;
			filter_values.push(applied_filters[key]);
		}
		print_settings.report_name = `${__(this.report_name)}_${filter_values.join("_")}.pdf`;
		influxframework.render_pdf(html, print_settings);
	}

	get_filters_html_for_print() {
		const applied_filters = this.get_filter_values();
		return Object.keys(applied_filters)
			.map((fieldname) => {
				const label = influxframework.query_report.get_filter(fieldname).df.label;
				const value = applied_filters[fieldname];
				return `<h6>${__(label)}: ${value}</h6>`;
			})
			.join("");
	}

	export_report() {
		if (this.export_dialog) {
			this.export_dialog.clear();
			this.export_dialog.show();
			return;
		}

		let export_dialog_fields = [
			{
				label: __("Select File Format"),
				fieldname: "file_format",
				fieldtype: "Select",
				options: ["Excel", "CSV"],
				default: "Excel",
				reqd: 1,
			},
		];

		if (this.tree_report) {
			export_dialog_fields.push({
				label: __("Include indentation"),
				fieldname: "include_indentation",
				fieldtype: "Check",
			});
		}

		this.export_dialog = influxframework.prompt(
			export_dialog_fields,
			({ file_format, include_indentation }) => {
				this.make_access_log("Export", file_format);
				if (file_format === "CSV") {
					const column_row = this.columns.reduce((acc, col) => {
						if (!col.hidden) {
							acc.push(__(col.label));
						}
						return acc;
					}, []);
					const data = this.get_data_for_csv(include_indentation);
					const out = [column_row].concat(data);

					influxframework.tools.downloadify(out, null, this.report_name);
				} else {
					let filters = this.get_filter_values(true);
					if (influxframework.urllib.get_dict("prepared_report_name")) {
						filters = Object.assign(
							influxframework.urllib.get_dict("prepared_report_name"),
							filters
						);
					}

					const visible_idx = this.datatable.bodyRenderer.visibleRowIndices;
					if (visible_idx.length + 1 === this.data.length) {
						visible_idx.push(visible_idx.length);
					}

					const args = {
						cmd: "influxframework.desk.query_report.export_query",
						report_name: this.report_name,
						custom_columns: this.custom_columns.length ? this.custom_columns : [],
						file_format_type: file_format,
						filters: filters,
						visible_idx,
						include_indentation,
					};

					open_url_post(influxframework.request.url, args);
				}
			},
			__("Export Report: {0}", [this.report_name]),
			__("Download")
		);
	}

	get_data_for_csv(include_indentation) {
		const rows = this.datatable.bodyRenderer.visibleRows;
		if (this.raw_data.add_total_row) {
			rows.push(this.datatable.bodyRenderer.getTotalRow());
		}
		return rows.map((row) => {
			const standard_column_count = this.datatable.datamanager.getStandardColumnCount();
			return row.slice(standard_column_count).map((cell, i) => {
				if (cell.column.fieldtype === "Duration") {
					cell.content = influxframework.utils.get_formatted_duration(cell.content);
				}
				if (include_indentation && i === 0) {
					cell.content = "   ".repeat(row.meta.indent) + (cell.content || "");
				}
				return cell.content || "";
			});
		});
	}

	get_data_for_print() {
		if (!this.data.length) {
			return [];
		}

		const rows = this.datatable.datamanager.rowViewOrder
			.map((index) => {
				if (this.datatable.bodyRenderer.visibleRowIndices.includes(index)) {
					return this.data[index];
				}
			})
			.filter(Boolean);

		if (this.raw_data.add_total_row) {
			let totalRow = this.datatable.bodyRenderer.getTotalRow().reduce((row, cell) => {
				row[cell.column.id] = cell.content;
				row.is_total_row = true;
				return row;
			}, {});

			rows.push(totalRow);
		}
		return rows;
	}

	get_columns_for_print(print_settings, custom_format) {
		let columns = [];

		if (print_settings && print_settings.columns) {
			columns = this.get_visible_columns().filter((column) =>
				print_settings.columns.includes(column.fieldname)
			);
		} else {
			columns = custom_format ? this.columns : this.get_visible_columns();
		}

		return columns;
	}

	get_menu_items() {
		let items = [
			{
				label: __("Refresh"),
				action: () => this.refresh(),
				class: "visible-xs",
			},
			{
				label: __("Edit"),
				action: () => influxframework.set_route("Form", "Report", this.report_name),
				condition: () => influxframework.user.is_report_manager(),
				standard: true,
			},
			{
				label: __("Print"),
				action: () => {
					let dialog = influxframework.ui.get_print_settings(
						false,
						(print_settings) => this.print_report(print_settings),
						this.report_doc.letter_head,
						this.get_visible_columns()
					);
					this.add_portrait_warning(dialog);
				},
				condition: () => influxframework.model.can_print(this.report_doc.ref_doctype),
				standard: true,
			},
			{
				label: __("PDF"),
				action: () => {
					let dialog = influxframework.ui.get_print_settings(
						false,
						(print_settings) => this.pdf_report(print_settings),
						this.report_doc.letter_head,
						this.get_visible_columns()
					);

					this.add_portrait_warning(dialog);
				},
				condition: () => influxframework.model.can_print(this.report_doc.ref_doctype),
				standard: true,
			},
			{
				label: __("Export"),
				action: () => this.export_report(),
				condition: () => influxframework.model.can_export(this.report_doc.ref_doctype),
				standard: true,
			},
			{
				label: __("Setup Auto Email"),
				action: () =>
					influxframework.set_route("List", "Auto Email Report", { report: this.report_name }),
				standard: true,
			},
			{
				label: __("Add Column"),
				action: () => {
					let d = new influxframework.ui.Dialog({
						title: __("Add Column"),
						fields: [
							{
								fieldtype: "Select",
								fieldname: "doctype",
								label: __("From Document Type"),
								options: this.linked_doctypes.map((df) => ({
									label: df.doctype,
									value: df.doctype,
								})),
								change: () => {
									let doctype = d.get_value("doctype");
									influxframework.model.with_doctype(doctype, () => {
										let options = influxframework.meta
											.get_docfields(doctype)
											.filter(influxframework.model.is_value_type)
											.map((df) => ({
												label: df.label,
												value: df.fieldname,
											}));

										d.set_df_property(
											"field",
											"options",
											options.sort(function (a, b) {
												if (a.label < b.label) {
													return -1;
												}
												if (a.label > b.label) {
													return 1;
												}
												return 0;
											})
										);
									});
								},
							},
							{
								fieldtype: "Select",
								label: __("Field"),
								fieldname: "field",
								options: [],
							},
							{
								fieldtype: "Select",
								label: __("Insert After"),
								fieldname: "insert_after",
								options: this.columns.map((df) => df.label),
							},
						],
						primary_action: (values) => {
							const custom_columns = [];
							let df = influxframework.meta.get_docfield(values.doctype, values.field);
							const insert_after_index = this.columns.findIndex(
								(column) => column.label === values.insert_after
							);
							custom_columns.push({
								fieldname: df.fieldname,
								fieldtype: df.fieldtype,
								label: df.label,
								insert_after_index: insert_after_index,
								link_field: this.doctype_field_map[values.doctype],
								doctype: values.doctype,
								options: df.options,
								width: 100,
							});

							this.custom_columns = this.custom_columns.concat(custom_columns);
							influxframework.call({
								method: "influxframework.desk.query_report.get_data_for_custom_field",
								args: {
									field: values.field,
									doctype: values.doctype,
								},
								callback: (r) => {
									const custom_data = r.message;
									const link_field = this.doctype_field_map[values.doctype];

									this.add_custom_column(
										custom_columns,
										custom_data,
										link_field,
										values.field,
										insert_after_index
									);
									d.hide();
								},
							});
							this.set_menu_items();
						},
					});

					d.show();
				},
				standard: true,
			},
			{
				label: __("User Permissions"),
				action: () =>
					influxframework.set_route("List", "User Permission", {
						doctype: "Report",
						name: this.report_name,
					}),
				condition: () => influxframework.model.can_set_user_permissions("Report"),
				standard: true,
			},
		];

		if (influxframework.user.is_report_manager()) {
			items.push({
				label: __("Save"),
				action: () => {
					let d = new influxframework.ui.Dialog({
						title: __("Save Report"),
						fields: [
							{
								fieldtype: "Data",
								fieldname: "report_name",
								label: __("Report Name"),
								default:
									this.report_doc.is_standard == "No" ? this.report_name : "",
								reqd: true,
							},
						],
						primary_action: (values) => {
							influxframework.call({
								method: "influxframework.desk.query_report.save_report",
								args: {
									reference_report: this.report_name,
									report_name: values.report_name,
									columns: this.get_visible_columns(),
								},
								callback: function (r) {
									this.show_save = false;
									d.hide();
									influxframework.set_route("query-report", r.message);
								},
							});
						},
					});
					d.show();
				},
				standard: true,
			});
		}

		return items;
	}

	add_portrait_warning(dialog) {
		if (this.columns.length > 10) {
			dialog.set_df_property("orientation", "change", () => {
				let value = dialog.get_value("orientation");
				let description =
					value === "Portrait"
						? __("Report with more than 10 columns looks better in Landscape mode.")
						: "";
				dialog.set_df_property("orientation", "description", description);
			});
		}
	}

	add_custom_column(custom_column, custom_data, link_field, column_field, insert_after_index) {
		const column = this.prepare_columns(custom_column);

		this.columns.splice(insert_after_index + 1, 0, column[0]);

		this.data.forEach((row) => {
			row[column_field] = custom_data[row[link_field]];
		});

		this.render_datatable();
	}

	get_linked_doctypes() {
		let doctypes = [];
		let dynamic_links = [];
		let dynamic_doctypes = new Set();
		this.doctype_field_map = {};

		this.columns.forEach((df) => {
			if (df.fieldtype == "Link" && df.options && df.options != "Currency") {
				doctypes.push({
					doctype: df.options,
					fieldname: df.fieldname,
				});
			} else if (df.fieldtype == "Dynamic Link" && df.options) {
				dynamic_links.push({
					link_name: df.options,
					fieldname: df.fieldname,
				});
			}
		});

		this.data.forEach((row) => {
			dynamic_links.forEach((field) => {
				if (row[field.link_name]) {
					dynamic_doctypes.add(row[field.link_name] + ":" + field.fieldname);
				}
			});
		});

		doctypes = doctypes.concat(
			Array.from(dynamic_doctypes).map((d) => {
				const doc_field_pair = d.split(":");
				return {
					doctype: doc_field_pair[0],
					fieldname: doc_field_pair[1],
				};
			})
		);

		doctypes.forEach((doc) => {
			this.doctype_field_map[doc.doctype] = doc.fieldname;
		});

		return doctypes;
	}

	setup_report_wrapper() {
		if (this.$report) return;

		// Remove border from
		$(".page-head-content").removeClass("border-bottom");

		let page_form = this.page.main.find(".page-form");
		this.$status = $(`<div class="form-message text-muted small"></div>`)
			.hide()
			.insertAfter(page_form);

		this.$summary = $(`<div class="report-summary"></div>`).hide().appendTo(this.page.main);

		this.$chart = $('<div class="chart-wrapper">').hide().appendTo(this.page.main);

		this.$loading = $(this.message_div("")).hide().appendTo(this.page.main);
		this.$report = $('<div class="report-wrapper">').appendTo(this.page.main);
		this.$message = $(this.message_div("")).hide().appendTo(this.page.main);
	}

	show_status(status_message) {
		this.$status.html(status_message).show();
	}

	hide_status() {
		this.$status.hide();
	}

	show_footer_message() {
		this.$report_footer && this.$report_footer.remove();
		this.$report_footer = $(`<div class="report-footer text-muted"></div>`).appendTo(
			this.page.main
		);
		if (this.tree_report) {
			this.$tree_footer = $(`<div class="tree-footer col-md-6">
				<button class="btn btn-xs btn-default" data-action="expand_all_rows">
					${__("Expand All")}</button>
				<button class="btn btn-xs btn-default" data-action="collapse_all_rows">
					${__("Collapse All")}</button>
			</div>`);
			$(this.$report_footer).append(this.$tree_footer);
			this.$tree_footer.find("[data-action=collapse_all_rows]").show();
			this.$tree_footer.find("[data-action=expand_all_rows]").hide();
		}

		const message = __(
			"For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)."
		);
		const execution_time_msg = __("Execution Time: {0} sec", [this.execution_time || 0.1]);

		this.$report_footer.append(`<div class="col-md-12">
			<span">${message}</span><span class="pull-right">${execution_time_msg}</span>
		</div>`);
	}

	expand_all_rows() {
		this.$tree_footer.find("[data-action=expand_all_rows]").hide();
		this.datatable.rowmanager.expandAllNodes();
		this.$tree_footer.find("[data-action=collapse_all_rows]").show();
	}

	collapse_all_rows() {
		this.$tree_footer.find("[data-action=collapse_all_rows]").hide();
		this.datatable.rowmanager.collapseAllNodes();
		this.$tree_footer.find("[data-action=expand_all_rows]").show();
	}

	message_div(message) {
		return `<div class='flex justify-center align-center text-muted' style='height: 50vh;'>
			<div>${message}</div>
		</div>`;
	}

	reset_report_view() {
		this.hide_status();
		this.toggle_nothing_to_show(true);
		this.refresh();
	}

	toggle_nothing_to_show(flag) {
		let message =
			this.prepared_report && !this.prepared_report_document
				? __(
						"This is a background report. Please set the appropriate filters and then generate a new one."
				  )
				: this.get_no_result_message();

		this.toggle_message(flag, message);

		if (flag && this.prepared_report) {
			this.prepared_report_action = "New";
			if (!this.primary_button.is(":visible")) {
				this.add_prepared_report_buttons();
			}
		}
	}

	toggle_message(flag, message) {
		if (flag) {
			this.$message.find("div").html(message);
			this.$message.show();
		} else {
			this.$message.hide();
		}
	}

	toggle_filter_display(fieldname, flag) {
		this.$page.find(`div[data-fieldname=${fieldname}]`).toggleClass("hide-control", flag);
	}

	toggle_report(flag) {
		this.$report.toggle(flag);
		this.$chart.toggle(flag);
		this.$summary.toggle(flag);
	}

	get_checked_items(only_docnames) {
		const indexes = this.datatable.rowmanager.getCheckedRows();

		return indexes.reduce((items, i) => {
			if (i === undefined) return items;

			const item = this.data[i];
			items.push(only_docnames ? item.name : item);
			return items;
		}, []);
	}

	// backward compatibility
	get get_values() {
		return this.get_filter_values;
	}
};

Object.defineProperty(influxframework, "query_report_filters_by_name", {
	get() {
		console.warn(
			"[Query Report] influxframework.query_report_filters_by_name is deprecated. Please use the new api: influxframework.query_report.get_filter_value(fieldname) and influxframework.query_report.set_filter_value(fieldname, value)"
		);
		return null;
	},
});
