'use strict';
'require view';
'require form';
'require rpc';
'require ui';
'require uci';
'require tools.widgets as widgets';

const callGetStatus = rpc.declare({ object: 'tailscale', method: 'get_status' });
const callGetSettings = rpc.declare({ object: 'tailscale', method: 'get_settings' });
const callDoLogin = rpc.declare({ object: 'tailscale', method: 'do_login', params: ['form_data'] });
const callDoLogout = rpc.declare({ object: 'tailscale', method: 'do_logout' });
const callGetSubroutes = rpc.declare({ object: 'tailscale', method: 'get_subroutes' });
const callSetupFirewall = rpc.declare({ object: 'tailscale', method: 'setup_firewall' });
let map;

const tailscaleSettingsConf = [
	[form.Flag, 'service_enabled', _('Enable Tailscale Service'), _('Enable or disable the Tailscale service. When disabled, the service will be stopped and the process will be killed.'), { rmempty: false }],
	[form.ListValue, 'fw_mode', _('Firewall Mode'), _('Select the firewall backend for Tailscale to use. Requires service restart to take effect.'), {values: ['nftables','iptables'],rmempty: false}],
	[form.Flag, 'accept_routes', _('Accept Routes'), _('Allow accepting routes announced by other nodes.'), { rmempty: false }],
	[form.Flag, 'advertise_exit_node', _('Advertise Exit Node'), _('Declare this device as an Exit Node.'), { rmempty: false }],
	[form.Flag, 'exit_node_allow_lan_access', _('Allow LAN Access'), _('When using the exit node, access to the local LAN is allowed.'), { rmempty: false }],
	[form.Flag, 'runwebclient', _('Enable Web Interface'), _('Expose a web interface on port 5252 for managing this node over Tailscale.'), { rmempty: false }],
	[form.Flag, 'nosnat', _('Disable SNAT'), _('Disable Source NAT (SNAT) for traffic to advertised routes. Most users should leave this unchecked.'), { rmempty: false }],
	[form.Flag, 'shields_up', _('Shields Up'), _('When enabled, blocks all inbound connections from the Tailscale network.'), { rmempty: false }],
	[form.Flag, 'ssh', _('Enable Tailscale SSH'), _('Allow connecting to this device through the SSH function of Tailscale.'), { rmempty: false }],
	[form.ListValue, 'dns_mode', _('DNS Mode'), _('Controls how Tailscale DNS is handled.')+'<br>'+_('Disabled: system DNS only.')+'<br>'+_('MagicDNS: Tailscale overrides resolv.conf.')+'<br>'+_('OpenWrt Forward: MagicDNS via dnsmasq forwarding.(Only support ts.net)'), { values: [['disabled', _('Disabled')], ['magicdns', 'MagicDNS'], ['openwrt_forward', _('OpenWrt Forward')]], rmempty: false }],
	[form.Flag, 'enable_relay', _('Enable Peer Relay'), _('Enable this device as a Peer Relay server. Requires a public IP and an UDP port open on the router.'), { rmempty: false }]
];

const accountConf = [];	// dynamic created in render function

const daemonConf = [
	//[form.Value, 'daemon_mtu', _('Daemon MTU'), _('Set a custom MTU for the Tailscale daemon. Leave blank to use the default value.'), { datatype: 'uinteger', placeholder: '1280' }, { rmempty: false }],
	[form.Flag, 'daemon_reduce_memory', _('(Experimental) Reduce Memory Usage'), _('Enabling this option can reduce memory usage, but it may sacrifice some performance (set GOGC=10).'), { rmempty: false }]
];

const derpMapUrl = 'https://controlplane.tailscale.com/derpmap/default';
let regionCodeMap = {};

// this function copy from luci-app-frpc. thx
function setParams(o, params) {
	if (!params) return;

	for (const [key, val] of Object.entries(params)) {
		if (key === 'values') {
			[].concat(val).forEach(v =>
				o.value.apply(o, Array.isArray(v) ? v : [v])
			);
		} else if (key === 'depends') {
			const arr = Array.isArray(val) ? val : [val];
			o.deps = arr.map(dep => Object.assign({}, ...o.deps, dep));
		} else {
			o[key] = val;
		}
	}

	if (params.datatype === 'bool')
		Object.assign(o, { enabled: 'true', disabled: 'false' });
}

// this function copy from luci-app-frpc. thx
function defTabOpts(s, t, opts, params) {
	for (let i = 0; i < opts.length; i++) {
		const opt = opts[i];
		const o = s.taboption(t, opt[0], opt[1], opt[2], opt[3]);
		setParams(o, opt[4]);
		setParams(o, params);
	}
}

function getRunningStatus() {
	return L.resolveDefault(callGetStatus(), { running: false }).then(function (res) {
		return res;
	});
}

function formatBytes(bytes) {
	const bytes_num = parseInt(bytes, 10);
	if (isNaN(bytes_num) || bytes_num === 0) return '-';
	const k = 1000;
	const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
	const i = Math.floor(Math.log(bytes_num) / Math.log(k));
	return parseFloat((bytes_num / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

function formatLastSeen(d) {
	if (!d) return _('N/A');
	if (d === '0001-01-01T00:00:00Z') return _('Now');
	const t = new Date(d);
	if (isNaN(t)) return _('Invalid Date');
	const diff = (Date.now() - t) / 1000;
	if (diff < 0) return t.toLocaleString();
	if (diff < 60) return _('Just now');

	const mins = diff / 60, hrs = mins / 60, days = hrs / 24;
	const fmt = (n, s, p) => `${Math.floor(n)} ${Math.floor(n) === 1 ? _(s) : _(p)} ${_('ago')}`;

	if (mins < 60) return fmt(mins, 'minute', 'minutes');
	if (hrs < 24) return fmt(hrs, 'hour', 'hours');
	if (days < 30) return fmt(days, 'day', 'days');

	return t.toISOString().slice(0, 10);
}

async function initializeRegionMap() {
	const cacheKey = 'tailscale_derp_map_cache';
	const ttl = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds

	try {
		const cachedItem = localStorage.getItem(cacheKey);
		if (cachedItem) {
			const cached = JSON.parse(cachedItem);
			// Check if the cached data is still valid (not expired)
			if (Date.now() - cached.timestamp < ttl) {
				regionCodeMap = cached.data;
				return;
			}
		}
	} catch (e) {
		ui.addTimeLimitedNotification(null, [ E('p', _('Error reading cached DERP region map: %s').format(e.message || _('Unknown error'))) ], 7000, 'error');
	}

	// If no valid cache, fetch from the network
	try {
		const response = await fetch(derpMapUrl);
		if (!response.ok) {
			return;
		}
		const data = await response.json();
		const newRegionMap = {};
		for (const regionId in data.Regions) {
			const region = data.Regions[regionId];
			const code = (region.RegionCode || '').toLowerCase();
			const name = region.RegionName || region.RegionCode || `Region ${regionId}`;
			newRegionMap[code] = name;
		}
		regionCodeMap = newRegionMap;

		// Save the newly fetched data to the cache
		try {
			const itemToCache = {
				timestamp: Date.now(),
				data: regionCodeMap
			};
			localStorage.setItem(cacheKey, JSON.stringify(itemToCache));
		} catch (e) {
			ui.addTimeLimitedNotification(null, [ E('p', _('Error caching DERP region map: %s').format(e.message || _('Unknown error'))) ], 7000, 'error');
		}
	} catch (error) {
		ui.addTimeLimitedNotification(null, [ E('p', _('Error fetching DERP region map: %s').format(error.message || _('Unknown error'))) ], 7000, 'error');
	}
}

function formatConnectionInfo(info) {
	if (!info) { return '-'; }
	if (typeof info === 'string' && info.length === 3) {
		const lowerCaseInfo = info.toLowerCase();
		return regionCodeMap[lowerCaseInfo] || info;
	}
	return info;
}

function renderStatus(status) {
	// If status object is not yet available, show a loading message.
	if (!status || !status.hasOwnProperty('status')) {
		return E('em', {}, _('Collecting data ...'));
	}

	const notificationId = 'tailscale_health_notification';
	let notificationElement = document.getElementById(notificationId);
	if (status.health != '') {
		const message = _('Tailscale Health Check: %s').format(status.health);
		if (notificationElement) {
			notificationElement.textContent = message;
		}
		else {
			let newNotificationContent = E('p', { 'id': notificationId }, message);
			ui.addNotification(null, newNotificationContent, 'info');
		}
	}else{
		try{
			notificationElement.parentNode.parentNode.remove();
		}catch(e){}
	}

	if (Object.keys(regionCodeMap).length === 0) {
		initializeRegionMap();
	}

	// --- Part 1: Handle non-running states ---

	// State: Tailscale binary not found.
	if (status.status == 'not_installed') {
		return E('dl', { 'class': 'cbi-value' }, [
			E('dt', {}, _('Service Status')),
			E('dd', {}, E('span', { 'style': 'color:red;' }, E('strong', {}, _('TAILSCALE NOT FOUND'))))
		]);
	}

	// State: Logged out, requires user action.
	if (status.status == 'logout') {
		return E('dl', { 'class': 'cbi-value' }, [
			E('dt', {}, _('Service Status')),
			E('dd', {}, [
				E('span', { 'style': 'color:orange;' }, E('strong', {}, _('LOGGED OUT'))),
				E('br'),
				E('span', {}, _('Please use the login button in the settings below to authenticate.'))
			])
		]);
	}

	// State: Service is installed but not running.
	if (status.status != 'running') {
		return E('dl', { 'class': 'cbi-value' }, [
			E('dt', {}, _('Service Status')),
			E('dd', {}, E('span', { 'style': 'color:red;' }, E('strong', {}, _('NOT RUNNING'))))
		]);
	}

	// --- Part 2: Render the full status display for a running service ---

	// A helper array to define the data for the main status table.
	const statusData = [
		{ label: _('Service Status'), value: E('span', { 'style': 'color:green;' }, E('strong', {}, _('RUNNING'))) },
		{ label: _('Version'), value: status.version || 'N/A' },
		{ label: _('TUN Mode'), value: status.TUNMode ? _('Enabled') : _('Disabled') },
		{ label: _('Tailscale IPv4'), value: status.ipv4 || 'N/A' },
		{ label: _('Tailscale IPv6'), value: status.ipv6 || 'N/A' },
		{ label: _('Tailnet Name'), value: status.domain_name || 'N/A' }
	];

	// Build the horizontal status table using the data array.
	const statusTable = E('table', { 'style': 'width: 100%; border-spacing: 0 5px;' }, [
		E('tr', {}, statusData.map(item => E('td', { 'style': 'padding-right: 20px;' }, E('strong', {}, item.label)))),
		E('tr', {}, statusData.map(item => E('td', { 'style': 'padding-right: 20px;' }, item.value)))
	]);

	return statusTable;
}

function renderDevices(status) {
	if (!status || !status.hasOwnProperty('status')) {
		return E('em', {}, _('Collecting data ...'));
	}

	if (status.status != 'running') {
		return E('em', {}, _('Tailscale status error'));
	}

	if (Object.keys(regionCodeMap).length === 0) {
		initializeRegionMap();
	}

	const peers = status.peers;
	if (!peers || Object.keys(peers).length === 0) {
		return E('p', {}, _('No peer devices found.'));
	}

	const peerTableHeaders = [
		{ text: _('Status'), style: 'width: 80px;' },
		{ text: _('Hostname') },
		{ text: _('Tailscale IP') },
		{ text: _('OS') },
		{ text: _('Connection Info') },
		{ text: _('RX') },
		{ text: _('TX') },
		{ text: _('Last Seen') }
	];

	return E('table', { 'class': 'cbi-table' }, [
		E('tr', { 'class': 'cbi-table-header' }, peerTableHeaders.map(header => {
			let th_style = 'padding-right: 20px; text-align: left;';
			if (header.style) {
				th_style += header.style;
			}
			return E('th', { 'class': 'cbi-table-cell', 'style': th_style }, header.text);
		})),

		...Object.entries(peers).map(([peerid, peer]) => {
			const td_style = 'padding-right: 20px;';

			return E('tr', { 'class': 'cbi-rowstyle-1' }, [
				E('td', { 'class': 'cbi-value-field', 'style': td_style },
					E('span', {
						'style': `color:${peer.exit_node ? 'blue' : (peer.online ? 'green' : 'gray')};`,
						'title': (peer.exit_node ? _('Exit Node') + ' ' : '') + (peer.online ? _('Online') : _('Offline'))
					}, peer.online ? '●' : '○')
				),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, E('strong', {}, peer.hostname + (peer.exit_node_option ? ' (ExNode)' : ''))),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, peer.ip || 'N/A'),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, peer.ostype || 'N/A'),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatConnectionInfo(peer.linkadress || '-')),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.rx)),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatBytes(peer.tx)),
				E('td', { 'class': 'cbi-value-field', 'style': td_style }, formatLastSeen(peer.lastseen))
			]);
		})
	]);
}

return view.extend({
	load() {
		return Promise.all([
			L.resolveDefault(callGetStatus(), { running: '', peers: [] }),
			L.resolveDefault(callGetSettings(), { accept_routes: false }),
			L.resolveDefault(callGetSubroutes(), { routes: [] })
		])
		.then(function([status, settings_from_rpc, subroutes]) {
			return uci.load('tailscale').then(function() {
				if (uci.get('tailscale', 'settings') === null) {
					// No existing settings found; initialize UCI with RPC settings
					uci.add('tailscale', 'settings', 'settings');
					uci.set('tailscale', 'settings', 'service_enabled', '1');
					uci.set('tailscale', 'settings', 'fw_mode', 'nftables');
					uci.set('tailscale', 'settings', 'accept_routes', (settings_from_rpc.accept_routes ? '1' : '0'));
					uci.set('tailscale', 'settings', 'advertise_exit_node', ((settings_from_rpc.advertise_exit_node || false) ? '1' : '0'));
					uci.set('tailscale', 'settings', 'advertise_routes', (settings_from_rpc.advertise_routes || []).join(', '));
					uci.set('tailscale', 'settings', 'exit_node', settings_from_rpc.exit_node || '');
					uci.set('tailscale', 'settings', 'exit_node_allow_lan_access', ((settings_from_rpc.exit_node_allow_lan_access || false) ? '1' : '0'));
					uci.set('tailscale', 'settings', 'ssh', ((settings_from_rpc.ssh || false) ? '1' : '0'));
					uci.set('tailscale', 'settings', 'shields_up', ((settings_from_rpc.shields_up || false) ? '1' : '0'));
					uci.set('tailscale', 'settings', 'runwebclient', ((settings_from_rpc.runwebclient || false) ? '1' : '0'));
					uci.set('tailscale', 'settings', 'nosnat', ((settings_from_rpc.nosnat || false) ? '1' : '0'));
					uci.set('tailscale', 'settings', 'dns_mode', 'disabled');

					uci.set('tailscale', 'settings', 'daemon_reduce_memory', '0');
					uci.set('tailscale', 'settings', 'daemon_mtu', '');
					return uci.save();
				}
			}).then(function() {
				// Migrate from old disable_magic_dns to dns_mode if needed
				if (uci.get('tailscale', 'settings', 'dns_mode') === null) {
					var oldMagicDns = uci.get('tailscale', 'settings', 'disable_magic_dns');
					uci.set('tailscale', 'settings', 'dns_mode', oldMagicDns === '1' ? 'disabled' : 'magicdns');
					uci.unset('tailscale', 'settings', 'disable_magic_dns');
					return uci.save();
				}
			}).then(function() {
				return [status, settings_from_rpc, subroutes];
			});
		});
	},

	render ([status = {}, settings = {}, subroutes_obj]) {
		const subroutes = (subroutes_obj && subroutes_obj.routes) ? subroutes_obj.routes : [];

		let s;
		map = new form.Map('tailscale', _('Tailscale'), _('Tailscale is a mesh VPN solution that makes it easy to connect your devices securely. This configuration page allows you to manage Tailscale settings on your OpenWrt device.'));

		s = map.section(form.NamedSection, '_status');
		s.anonymous = true;
		s.render = function (section_id) {
			L.Poll.add(
				function () {
					return getRunningStatus().then(function (res) {
						const view = document.getElementById("service_status_display");
						if (view) {
							const content = renderStatus(res);
							view.replaceChildren(content);
						}

						const devicesView = document.getElementById("tailscale_devices_display");
						if (devicesView) {
							devicesView.replaceChildren(renderDevices(res));
						}

						// login button only available when logged out
						const login_btn=document.getElementsByClassName('cbi-button cbi-button-apply')[0];
						if(login_btn) { login_btn.disabled=(res.status != 'logout'); }
					});
				}, 10);

			return E('div', { 'id': 'service_status_display', 'class': 'cbi-value' },
				_('Collecting data ...')
			);
		}

		// Bind settings to the 'settings' section of uci
		s = map.section(form.NamedSection, 'settings', 'settings', null);
		s.dynamic = true;

		// Create the "General Settings" tab and apply tailscaleSettingsConf
		s.tab('general', _('General Settings'));

		defTabOpts(s, 'general', tailscaleSettingsConf, { optional: false });

		const relayPort = s.taboption('general', form.Value, 'relay_server_port', _('Peer Relay Port'),
			_('UDP port for the Peer Relay service. Open this port on your router firewall/NAT.')
		);
		relayPort.datatype = 'port';
		relayPort.placeholder = '40000';
		relayPort.rmempty = false;
		relayPort.depends('enable_relay', '1');

		const en = s.taboption('general', form.ListValue, 'exit_node', _('Exit Node'), _('Select an exit node from the list. If enabled, Allow LAN Access is enabled implicitly.'));
		en.value('', _('None'));
		if (status.peers) {
			Object.values(status.peers).forEach(function(peer) {
				if (peer.exit_node_option) {
					const primaryIp = peer.ip.split('<br>')[0];
					const label = peer.hostname ? `${peer.hostname} (${primaryIp})` : primaryIp;
					en.value(primaryIp, label);
				}
			});
		}
		en.rmempty = true;
		en.cfgvalue = function(section_id) {
			if (status && status.status === 'running' && status.peers) {
				for (const id in status.peers) {
					if (status.peers[id].exit_node) {
						return status.peers[id].ip.split('<br>')[0];
					}
				}
				return '';
			}
			return uci.get('tailscale', 'settings', 'exit_node') || '';
		};

		const o = s.taboption('general', form.DynamicList, 'advertise_routes', _('Advertise Routes'),_('Advertise subnet routes behind this device. Select from the detected subnets below or enter custom routes (comma-separated).'));
		if (subroutes.length > 0) {
			subroutes.forEach(function(subnet) {
				o.value(subnet, subnet);
			});
		}
		o.rmempty = true;

		const fwBtn = s.taboption('general', form.Button, '_setup_firewall', _('Auto Configure Firewall'));
		fwBtn.description = _('Essential configuration for Subnet Routing (Site-to-Site) and Exit Node features.')
		+'<br>'+_('It automatically creates the tailscale interface, sets up firewall zones for LAN <-> Tailscale forwarding,')
		+'<br>'+_('and enables Masquerading and MSS Clamping (MTU fix) to ensure stable connections.');
		fwBtn.inputstyle = 'action';
		fwBtn.onclick = function() {
			return callSetupFirewall().then(function(res) {
			const msg = res?.message || _('Firewall configuration applied.');
			ui.addNotification(null, E('p', {}, msg), 'info');
		}).catch(function(err) {
			ui.addNotification(null, E('p', {}, _('Failed to configure firewall: %s').format(err?.message || err || 'Unknown error')), 'error');
		}).then(function() {
			return new Promise(function(resolve) {
				window.setTimeout(resolve, 3000);
			});
		});
	};

		const helpTitle = s.taboption('general', form.DummyValue, '_help_title');
		helpTitle.title = _('How to enable Site-to-Site?');
		helpTitle.render = function() {
			return E('div', { 'class': 'cbi-value', 'style': 'margin-top: 1em; border-top: 1px font-weight: bold;' }, [
				E('label', { 'class': 'cbi-value-title' }, this.title),
				E('div', { 'class': 'cbi-value-field', 'style': 'line-height: 1.6em; font-size: 95%; color: #555;' }, [
					_('1. Select "Accept Routes" (to access remote devices).'), E('br'),
					_('2. In "Advertise Routes", select your local subnet (to allow remote devices to access this LAN).'), E('br'),
					_('3. Click "Auto Configure Firewall" (to allow traffic forwarding).'), E('br'),
					E('strong', { 'style': 'color: #d9534f;' }, _('[Important] Log in to the Tailscale admin console and manually enable "Subnet Routes" for this device.'))
				])
			]);
		};

		// Create the account settings
		s.tab('account', _('Account Settings'));
		defTabOpts(s, 'account', accountConf, { optional: false });

		const loginBtn = s.taboption('account', form.Button, '_login', _('Login'),
		_('Click to get a login URL for this device.')
		+'<br>'+_('If the timeout is displayed, you can refresh the page and click Login again.'));
		loginBtn.inputstyle = 'apply';

		const customLoginUrl = s.taboption('account', form.Value, 'custom_login_url',
			_('Custom Login Server'),
			_('Optional: Specify a custom control server URL (e.g., a Headscale instance, %s).'.format('https://example.com'))
			+'<br>'+_('Leave blank for default Tailscale control plane.')
		);
		customLoginUrl.placeholder = '';
		customLoginUrl.rmempty = true;

		const customLoginAuthKey = s.taboption('account', form.Value, 'custom_login_AuthKey',
			_('Custom Login Server Auth Key'),
			_('Optional: Specify an authentication key for the custom control server. Leave blank if not required.')
			+'<br>'+_('If you are using custom login server but not providing an Auth Key, will redirect to the login page without pre-filling the key.')
		);
		customLoginAuthKey.placeholder = '';
		customLoginAuthKey.rmempty = true;

		const logoutBtn = s.taboption('account', form.Button, '_logout', _('Logout'),
		_('Click to Log out account on this device.')
		+'<br>'+_('Disconnect from Tailscale and expire current node key.'));
		logoutBtn.inputstyle = 'apply';
		logoutBtn.id = 'tailscale_logout_btn';

		loginBtn.onclick = function() {
			const customServerInput = document.getElementById('widget.cbid.tailscale.settings.custom_login_url');
			const customServer = customServerInput ? customServerInput.value : '';
			const customserverAuthInput = document.getElementById('widget.cbid.tailscale.settings.custom_login_AuthKey');
			const customServerAuth = customserverAuthInput ? customserverAuthInput.value : '';
			const loginWindow = window.open('', '_blank');
			if (!loginWindow) {
				ui.addTimeLimitedNotification(null, [ E('p', _('Could not open a new tab. Please check if your browser or an extension blocked the pop-up.')) ], 10000, 'error');
				return;
			}
			// Display a prompt message in the new window
			const doc = loginWindow.document;
			doc.body.innerHTML =
				'<h2>' + _('Tailscale Login') + '</h2>' +
				'<p>' + _('Requesting Tailscale login URL... Please wait.') + '</p>' +
				'<p>' + _('This can take up to 30 seconds.') + '</p>';

			ui.showModal(_('Requesting Login URL...'), E('em', {}, _('Please wait.')));
			const payload = {
				loginserver: customServer || '',
				loginserver_authkey: customServerAuth || ''
			};
			// Show a "loading" modal and execute the asynchronous RPC call
			ui.showModal(_('Requesting Login URL...'), E('em', {}, _('Please wait.')));
			return callDoLogin(payload).then(function(res) {
				ui.hideModal();
				if (res && res.url) {
					// After successfully obtaining the URL, redirect the previously opened tab
					loginWindow.location.href = res.url;
				} else {
					// If it fails, inform the user and they can close the new tab
					doc.body.innerHTML =
						'<h2>' + _('Error') + '</h2>' +
						'<p>' + _('Failed to get login URL. You may close this tab.') + '</p>';
					ui.addTimeLimitedNotification(null, [ E('p', _('Failed to get login URL: Invalid response from server.')) ], 7000, 'error');
				}
			}).catch(function(err) {
				ui.hideModal();
				ui.addTimeLimitedNotification(null, [ E('p', _('Failed to get login URL: %s').format(err.message || _('Unknown error'))) ], 7000, 'error');
			});
		};

		logoutBtn.onclick = function() {
			const confirmationContent = E([
				E('p', {}, _('Are you sure you want to log out?')
					+'<br>'+_('This will disconnect this device from your Tailnet and require you to re-authenticate.')),

				E('div', { 'style': 'text-align: right; margin-top: 1em;' }, [
					E('button', {
						'class': 'cbi-button',
						'click': ui.hideModal
					}, _('Cancel')),
					' ',
					E('button', {
						'class': 'cbi-button cbi-button-negative',
						'click': function() {
							ui.hideModal();
							ui.showModal(_('Logging out...'), E('em', {}, _('Please wait.')));

							return callDoLogout().then(function(res) {
								ui.hideModal();
								ui.addTimeLimitedNotification(null, [ E('p', _('Successfully logged out.')) ], 5000, 'info');
							}).catch(function(err) {
								ui.hideModal();
								ui.addTimeLimitedNotification(null, [ E('p', _('Logout failed: %s').format(err.message || _('Unknown error'))) ], 7000, 'error');
							});
						}
					}, _('Logout'))
				])
			]);
			ui.showModal(_('Confirm Logout'), confirmationContent);
		};

		s.tab('devices', _('Devices List'));
		const devicesSection = s.taboption('devices', form.DummyValue, '_devices');
		devicesSection.render = function () {
			return E('div', { 'id': 'tailscale_devices_display', 'class': 'cbi-value' }, renderDevices(status));
		};

		// Create the "Daemon Settings" tab and apply daemonConf
		//s.tab('daemon', _('Daemon Settings'));
		//defTabOpts(s, 'daemon', daemonConf, { optional: false });

		return map.render();
	},

	// The handleSaveApply function saves UCI changes then applies them via the
	// standard OpenWrt apply mechanism, which triggers /etc/init.d/tailscale-settings
	// and provides automatic rollback protection if the device becomes unreachable.
	handleSaveApply(ev, mode) {
		return map.save().then(function () {
			return ui.changes.apply(mode == '0');
		});
	},

	handleSave: null,
	handleReset: null
});
