/**
 * dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
 * Copyright (C) 2021-2025 Aleksey Andreev (liuch)
 *
 * Available at:
 * https://github.com/liuch/dmarc-srg
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of  MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

class Settings {
	constructor() {
		this._page = null;
		this._table = null;
		this._scroll = null;
		this._sort   = "ascent";
		this._element = document.getElementById("main-block");
	}

	display() {
		this._make_page_container();
		this._make_scroll_container();
		this._make_table();
		this._scroll.append(this._table.element());
		this._page.append(this._scroll);
		this._element.appendChild(this._page);
		this._table.focus();
	}

	update() {
		this._fetch_settings();
	}

	title() {
		return "Advanced Settings";
	}

	_fetch_settings() {
		this._table.display_status("wait");
		let that = this;

		let uparams = new URLSearchParams();
		uparams.set("direction", this._sort);

		return window.fetch("settings.php?" + uparams.toString(), {
			method: "GET",
			cache: "no-store",
			headers: HTTP_HEADERS,
			credentials: "same-origin"
		}).then(function(resp) {
			if (!resp.ok)
				throw new Error("Failed to fetch the settings");
			return resp.json();
		}).then(function(data) {
			that._table.display_status(null);
			Common.checkResult(data);
			let d = { more: data.more };
			d.rows = data.settings.map(function(it) {
				return that._make_row_data(it);
			});
			that._table.clear();
			let fr = new ITableFrame(d, 0);
			that._table.add_frame(fr);
			that._table.focus();
		}).catch(function(err) {
			Common.displayError(err);
			that._table.display_status("error", err.message);
		});
	}

	_make_page_container() {
		this._page = document.createElement("div");
		this._page.classList.add("page-container");
	}

	_make_scroll_container() {
		this._scroll = document.createElement("div");
		this._scroll.classList.add("table-wrapper");
	}

	_make_table() {
		this._table = new ITable({
			class:   "main-table small-cards",
			onclick: function(row) {
				let data = row.userdata();
				if (data) {
					this._display_edit_dialog(data);
				}
			}.bind(this),
			onsort: function(col) {
				let dir = col.sorted() && "toggle" || "ascent";
				this._table.set_sorted(col.name(), dir);
				this._sort = col.sorted();
				this.update();
			}.bind(this),
			onfocus: function(el) {
				scroll_to_element(el, this._scroll);
			}.bind(this)
		});
		[
			{ content: "Name", name: "name", sortable: true },
			{ content: "Value", name: "value" },
			{ content: "Description", name: "descr" }
		].forEach(function(col) {
			let c = this._table.add_column(col);
			if (c.name() === "name") {
				c.sort(this._sort);
			}
		}, this);
	}

	_make_row_data(d) {
		let rd = { cells: [], userdata: d.name };
		rd.cells.push({ content: d.name, class: "setting-name", label: "Name " });
		rd.cells.push({ content: d.value, class: "setting-value", label: "Value " });
		rd.cells.push({
			content: Settings._descriptions_short[d.name] || Settings._descriptions[d.name] || "No description",
			label: "Description "
		});
		if (d.value !== d.default) {
			rd.class = "custom-value";
		}
		return rd;
	}

	_display_edit_dialog(name) {
		let dlg = new SettingEditDialog({
			name:        name,
			description: Settings._descriptions[name]
		});
		this._element.appendChild(dlg.element());
		let that = this;
		dlg.show().then(function(d) {
			if (d) {
				that.update();
			}
		}).finally(function() {
			dlg.element().remove();
			that._table.focus();
		});
	}

	static _descriptions = {
		"status.emails-for-last-n-days": "The period in days for which statistics are displayed in the status block.",
		"status.emails-filter-when-list-filtered": "Applying the filter to the status block when filtering the report list.",
		"report-view.sort-records-by": "How records are sorted in the report view dialog.",
		"report-view.filter.initial-value": "The initial value of the record filter in the report view dialog. from-list - use current filter value of the report list. last-value - use the previous filter value. last-value-tab - use the previous filter value of the active tab.",
		"log-view.sort-list-by": "How report log items are sorted by default in the log view dialog.",
		"ui.datetime.offset": "Time zone offset of displayed dates in UI. Auto means that the report range is in UTC and all other dates are in local.",
		"ui.ipv4.url": "The URL that will be used as a link when clicking on the IPv4 address. For example: https://somewhoisservice.net/ip/{$ip}, where {$ip} is IP address from the UI. Use {$eip} if you want to insert url encoded IP address. Use an empty string to disable. Prefix the link template with ! or !! to open such links in one separate tab or each link in a separate tab, respectively. If there is no prefix, the link opens in the current tab.",
		"ui.ipv6.url": "The URL that will be used as a link when clicking on the IPv6 address. For example: https://somewhoisservice.net/ip/{$ip}, where {$ip} is IP address from the UI. Use {$eip} if you want to insert url encoded IP address. Use an empty string to disable. Prefix the link template with ! or !! to open such links in one separate tab or each link in a separate tab, respectively. If there is no prefix, the link opens in the current tab."
	};

	static _descriptions_short = {
		"report-view.filter.initial-value": "The initial value of the record filter in the report view dialog.",
		"ui.datetime.offset": "Time zone offset of displayed dates in UI.",
		"ui.ipv4.url": "The URL that will be used as a link when clicking on the IPv4 address.",
		"ui.ipv6.url": "The URL that will be used as a link when clicking on the IPv6 address."
	};
}

class SettingEditDialog extends VerticalDialog {
	constructor(param) {
		super({ title: "Setting dialog", buttons: [ "ok", "close" ] });
		this._data    = param || {};
		this._content = null;
		this._val_el  = null;
		this._val_tp  = null;
		this._desc_el = null;
		this._save_bt = null;
		this._fetched = false;
	}

	_gen_content() {
		let nm = document.createElement("input");
		nm.setAttribute("type", "text");
		nm.setAttribute("disabled", "disabled");
		nm.setAttribute("value", this._data.name);
		this._insert_input_row("Name", nm);

		let val = document.createElement("input");
		val.setAttribute("type", "text");
		val.disabled = true;
		this._insert_input_row("Value", val);
		this._val_el = val;
		this._val_tp = "string";

		let desc = document.createElement("textarea");
		desc.setAttribute("disabled", "disabled");
		if (this._data.description) {
			desc.appendChild(document.createTextNode(this._data.description));
		}
		desc.classList.add("description");
		this._insert_input_row("Description", desc);
		this._desc_el = desc;

		this._save_bt = this._buttons[1];
		this._save_bt.disabled = true;

		this._inputs.addEventListener("input", function(event) {
			if (this._fetched && event.target == this._val_el) {
				let e_val = null;
				switch (this._val_tp) {
					case "select":
						e_val = this._val_el.value;
						break;
					case "integer":
						e_val = this._val_el.valueAsNumber;
						break;
				}
				this._save_bt.disabled = (e_val === this._data.value);
			}
		}.bind(this));

		this._fetch_data();
	}

	_add_button(container, text, type) {
		if (type == "submit") {
			text = "Save";
		}
		super._add_button(container, text, type);
	}

	_fetch_data() {
		this._enable_ui(false);
		this.display_status("wait", "Getting data...");
		let uparams = new URLSearchParams();
		uparams.set("name", this._data.name);

		window.fetch("settings.php?" + uparams.toString(), {
			method: "GET",
			cache: "no-store",
			headers: HTTP_HEADERS,
			credentials: "same-origin"
		}).then(resp => {
			if (!resp.ok) throw new Error("Failed to fetch setting data for " + this._data.name);
			return resp.json();
		}).then(data => {
			Common.checkResult(data);
			this._data.value = data.value;
			this._update_ui(data);
			this._enable_ui(true);
			this._fetched = true;
		}).catch(err => {
			Common.displayError(err);
			this.display_status("error", err.message);
		}).finally(() => {
			this.display_status("wait", null);
		});
	}

	_enable_ui(en) {
		this._val_el.disabled = !en;
		this.focus();
	}

	_update_ui(data) {
		if (data.type !== this._val_tp) {
			let new_el = null;
			if (data.type == "integer") {
				new_el = document.createElement("input");
				new_el.setAttribute("type", "number");
				if (typeof(data.minimum) == "number") {
					new_el.setAttribute("min", data.minimum);
				}
				if (typeof(data.maximum) == "number") {
					new_el.setAttribute("max", data.maximum);
				}
			} else if (data.type == "select") {
				new_el = document.createElement("select");
				data.options.forEach(function(op) {
					let opt_el = document.createElement("option");
					opt_el.setAttribute("value", op);
					opt_el.appendChild(document.createTextNode(op));
					if (op === data.value) {
						opt_el.setAttribute("selected", "selected");
					}
					new_el.appendChild(opt_el);
				});
			}
			if (new_el) {
				new_el.setAttribute("required", "required");
				this._val_el.replaceWith(new_el);
				this._val_el = new_el;
			}
			this._val_tp = data.type;
		}
		this._val_el.value = data.value;
	}

	_submit() {
		this._save_bt.disabled = true;
		this._enable_ui(false);
		this.display_status("wait", "Getting data...");

		const body = {};
		body.name = this._data.name;
		if (this._val_tp == "integer") {
			body.value = this._val_el.valueAsNumber;
		} else {
			body.value = this._val_el.value;
		}
		body.action = "update";

		window.fetch("settings.php", {
			method: "POST",
			cache: "no-store",
			headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
			credentials: "same-origin",
			body: JSON.stringify(body)
		}).then(resp => {
			if (!resp.ok) throw new Error("Failed to update the setting");
			return resp.json();
		}).then(data => {
			Common.checkResult(data);
			this._data.value = this._val_el.value;
			this._result = body;
			this.hide();
			Notification.add({ type: "info", text: (data.message || "Updated successfully!") });
		}).catch(err => {
			Common.displayError(err);
			this.display_status("error", err.message);
		}).finally(() => {
			this.display_status("wait", null);
			this._save_bt.disabled = false;
			this._enable_ui(true);
		});
	}
}
