/*
 * Plugin GetOLT
 */
'use strict';

$$.getolt = new function() {
	const debug = $$.debug('getolt');

	const REBOOT_COOLDOWN = 5 * 60 * 1000; // 5 minutes in ms

	const rebootTimers = {};
	let escapeListenerAdded = false;

	// Helper: find container from any child element (button click context)
	const containerOf = (el) => el.closest('.getolt-container');

	// Initialize reboot buttons within container
	const initRebootButtons = (ctx) => {
		const buttons = ctx.querySelectorAll('.getolt-btn-reboot');
		buttons.forEach(function(btn) {
			checkRebootCooldown(btn);
		});
	};

	// Check and apply cooldown for a reboot button
	const checkRebootCooldown = (btn) => {
		const key = btn.getAttribute('data-reboot-key');
		const storageKey = 'getolt_reboot_' + key;
		const lastReboot = localStorage.getItem(storageKey);

		if (lastReboot) {
			const lastTime = parseInt(lastReboot, 10);
			const now = Date.now();
			const elapsed = now - lastTime;
			const remaining = REBOOT_COOLDOWN - elapsed;

			if (remaining > 0) {
				setRebootCooldown(btn, remaining);
			} else {
				localStorage.removeItem(storageKey);
			}
		}
	};

	// Set button to cooldown state
	const setRebootCooldown = (btn, remainingMs) => {
		const key = btn.getAttribute('data-reboot-key');
		btn.disabled = true;
		btn.classList.add('getolt-cooldown');
		updateRebootTooltip(btn, remainingMs);

		if (rebootTimers[key]) {
			clearInterval(rebootTimers[key]);
		}

		rebootTimers[key] = setInterval(function() {
			remainingMs -= 1000;
			if (remainingMs <= 0) {
				clearInterval(rebootTimers[key]);
				delete rebootTimers[key];
				btn.disabled = false;
				btn.classList.remove('getolt-cooldown');
				btn.textContent = 'Перезагрузить ONU';
				btn.title = '';
				localStorage.removeItem('getolt_reboot_' + key);
			} else {
				updateRebootTooltip(btn, remainingMs);
			}
		}, 1000);
	};

	// Update button text and tooltip with remaining time
	const updateRebootTooltip = (btn, remainingMs) => {
		const mins = Math.floor(remainingMs / 60000);
		const secs = Math.floor((remainingMs % 60000) / 1000);
		const timeStr = mins + ':' + (secs < 10 ? '0' : '') + secs;
		btn.textContent = 'Ожидание ' + timeStr;
		btn.title = 'Повторная перезагрузка возможна через ' + mins + ' мин. ' + secs + ' сек.';
	};

	// Check for cyrillic letters that look like latin (A, B, C, E)
	const hasCyrillicLookalikes = (mac) => {
		return /[\u0410\u0412\u0421\u0415\u0430\u0432\u0441\u0435]/.test(mac);
	};

	// Validate MAC format
	const isValidMac = (mac) => {
		const clean = mac.replace(/[:\-.\s]/g, '');
		return /^[0-9A-Fa-f]{12}$/.test(clean);
	};

	// Normalize MAC to XX:XX:XX:XX:XX:XX format
	const normalizeMac = (mac) => {
		const clean = mac.replace(/[^0-9A-Fa-f]/g, '').toUpperCase();
		if (clean.length !== 12) return mac;
		return clean.match(/.{2}/g).join(':');
	};

	// Show activation status
	const showActivationStatus = (ctx, message, type) => {
		const status = ctx.querySelector('.getolt-activation-status');
		if (!status) return;
		status.textContent = message;
		status.className = 'getolt-activation-status getolt-active getolt-' + type;
	};

	// Hide activation status
	const hideActivationStatus = (ctx) => {
		const status = ctx.querySelector('.getolt-activation-status');
		if (!status) return;
		status.className = 'getolt-activation-status';
	};

	const showError = (btn, statusBar, message) => {
		btn.disabled = false;
		statusBar.style.display = 'flex';
		statusBar.className = 'getolt-refresh-status-bar getolt-active getolt-error';
		statusBar.querySelector('.getolt-spinner').style.display = 'none';
		statusBar.querySelector('.getolt-status-text').textContent = message;
		setTimeout(function() {
			statusBar.style.display = 'none';
			statusBar.className = 'getolt-refresh-status-bar';
			statusBar.querySelector('.getolt-spinner').style.display = '';
		}, 5000);
	};

	const reloadTab = (btn) => {
		const tabContent = $(btn).closest('.ui-tabs-panel');
		if (tabContent.length) {
			const tabs = tabContent.closest('.ui-tabs');
			const tabIndex = tabs.find('.ui-tabs-panel').index(tabContent);
			const tabLink = tabs.find('.ui-tabs-nav li').eq(tabIndex).find('a');
			if (tabLink.length && tabLink.attr('href')) {
				$$.ajax.load(tabLink.attr('href'), tabContent);
			}
		}
	};

	// Close service modal within container
	const closeServiceModalIn = (ctx) => {
		const modal = ctx.querySelector('.getolt-service-modal-overlay');
		if (modal) {
			modal.classList.remove('getolt-active');
		}
	};

	// Show service selection modal
	const showServiceModal = (ctx, services, mac) => {
		const modal = ctx.querySelector('.getolt-service-modal-overlay');
		const list = ctx.querySelector('.getolt-service-list');

		if (!modal || !list) return;

		list.innerHTML = '';
		services.forEach(function(svc) {
			const li = document.createElement('li');
			li.setAttribute('data-service-id', svc.serviceId);
			li.onclick = function() {
				closeServiceModalIn(ctx);
				doActivateOnu(ctx, svc.serviceId, mac);
			};

			const title = document.createElement('div');
			title.className = 'getolt-service-title';
			title.textContent = svc.displayTitle || svc.login || ('Услуга #' + svc.serviceId);
			li.appendChild(title);

			if (svc.mac) {
				const macDiv = document.createElement('div');
				macDiv.className = 'getolt-service-mac';
				macDiv.textContent = 'Текущий MAC: ' + svc.mac;
				li.appendChild(macDiv);
			}

			list.appendChild(li);
		});

		modal.classList.add('getolt-active');
	};

	// Close service modal (public, called from JSP onclick)
	this.closeServiceModal = (el) => {
		const ctx = containerOf(el);
		if (ctx) closeServiceModalIn(ctx);
	};

	// === Public methods (called from JSP onclick handlers) ===

	this.startOnuReboot = (btn) => {
		const oltId = btn.getAttribute('data-olt-id');
		const port = btn.getAttribute('data-port');
		const onuId = btn.getAttribute('data-onu-id');
		const mac = btn.getAttribute('data-mac') || '';
		const processId = btn.getAttribute('data-process-id') || '';
		const key = btn.getAttribute('data-reboot-key');

		if (!confirm('Вы уверены, что хотите перезагрузить ONU ' + port + ':' + onuId + '?')) {
			return;
		}

		btn.disabled = true;
		btn.textContent = 'Перезагрузка...';
		debug('Starting reboot for oltId', oltId, 'port', port, 'onuId', onuId, 'processId', processId);

		let url = '/user/plugin/getolt/getolt.do?method=rebootOnu&oltId=' + oltId + '&port=' + port + '&onuId=' + onuId;
		if (processId) url += '&processId=' + processId;
		if (mac) url += '&mac=' + encodeURIComponent(mac);
		$$.ajax.post(url, {})
			.done(function(response) {
				debug('Reboot response', response);
				const result = response.data && response.data.data ? response.data.data : response.data;
				debug('reboot result =', result);
				if (result && result.success) {
					localStorage.setItem('getolt_reboot_' + key, Date.now().toString());

					btn.textContent = 'Перезагружена!';
					btn.style.background = '#28a745';
					setTimeout(function() {
						btn.style.background = '';
						setRebootCooldown(btn, REBOOT_COOLDOWN);
					}, 2000);
				} else {
					const msg = (result && result.message) ? result.message : (response.message || 'Ошибка перезагрузки');
					debug('Reboot error', msg);

					if (msg.indexOf('Подождите') !== -1) {
						const match = msg.match(/(\d+)\s*мин\.\s*(\d+)\s*сек/);
						if (match) {
							const mins = parseInt(match[1], 10);
							const secs = parseInt(match[2], 10);
							const remainingMs = (mins * 60 + secs) * 1000;
							localStorage.setItem('getolt_reboot_' + key, (Date.now() - REBOOT_COOLDOWN + remainingMs).toString());
							setRebootCooldown(btn, remainingMs);
						} else {
							alert('Ошибка: ' + msg);
							btn.disabled = false;
							btn.textContent = 'Перезагрузить ONU';
						}
					} else {
						alert('Ошибка: ' + msg);
						btn.disabled = false;
						btn.textContent = 'Перезагрузить ONU';
					}
				}
			})
			.fail(function(xhr, status, error) {
				debug('Reboot AJAX fail', status, error);
				alert('Ошибка соединения с сервером');
				btn.disabled = false;
				btn.textContent = 'Перезагрузить ONU';
			});
	};

	this.startPortRefresh = (btn) => {
		const ctx = containerOf(btn);
		const oltIp = btn.getAttribute('data-olt-ip');
		const port = btn.getAttribute('data-port');
		const processId = btn.getAttribute('data-process-id') || '';
		const key = btn.getAttribute('data-refresh-key');
		const statusBar = ctx.querySelector('.getolt-refresh-status-bar[data-refresh-key="' + key + '"]');

		if (!statusBar) return;

		btn.disabled = true;
		statusBar.style.display = 'flex';
		statusBar.className = 'getolt-refresh-status-bar getolt-active';
		statusBar.querySelector('.getolt-status-text').textContent = 'Запуск обновления...';
		debug('Starting refresh for', oltIp, 'port', port, 'key', key, 'processId', processId);

		let url = '/user/plugin/getolt/getolt.do?method=refreshPort&oltIp=' + encodeURIComponent(oltIp) + '&portNumber=' + port;
		if (processId) url += '&processId=' + processId;
		$$.ajax.post(url, {})
			.done(function(response) {
				debug('Response', response);
				const result = response.data && response.data.data ? response.data.data : response.data;
				debug('result =', result);
				if (result && result.success) {
					statusBar.querySelector('.getolt-status-text').textContent = 'Обновление завершено!';
					statusBar.className = 'getolt-refresh-status-bar getolt-active getolt-success';
					statusBar.querySelector('.getolt-spinner').style.display = 'none';
					setTimeout(function() { reloadTab(btn); }, 1500);
				} else {
					const msg = (result && result.message) ? result.message : (response.message || 'Ошибка запуска');
					debug('Error', msg);
					showError(btn, statusBar, msg);
				}
			})
			.fail(function(xhr, status, error) {
				debug('AJAX fail', status, error);
				showError(btn, statusBar, 'Ошибка соединения с сервером');
			});
	};

	this.formatMacInput = (input, e) => {
		const ctx = containerOf(input);
		const cursorPos = input.selectionStart;
		const oldValue = input.value;

		if (hasCyrillicLookalikes(oldValue)) {
			input.classList.add('getolt-is-invalid');
			const status = ctx.querySelector('.getolt-activation-status');
			if (status) {
				status.textContent = 'Кириллица! Переключите раскладку на EN';
				status.className = 'getolt-activation-status getolt-active getolt-error';
			}
		} else {
			input.classList.remove('getolt-is-invalid');
			const status = ctx.querySelector('.getolt-activation-status');
			if (status && status.textContent.indexOf('Кириллица') !== -1) {
				status.className = 'getolt-activation-status';
			}
		}

		const hex = oldValue.replace(/[^0-9A-Fa-f]/g, '').toUpperCase().substring(0, 12);

		let formatted = '';
		for (let i = 0; i < hex.length; i++) {
			if (i > 0 && i % 2 === 0) {
				formatted += ':';
			}
			formatted += hex[i];
		}

		if (input.value !== formatted) {
			input.value = formatted;

			let newPos = cursorPos;
			const oldColons = (oldValue.substring(0, cursorPos).match(/:/g) || []).length;
			const newColons = (formatted.substring(0, Math.min(cursorPos, formatted.length)).match(/:/g) || []).length;
			newPos += (newColons - oldColons);

			if (formatted.length > oldValue.length && formatted[cursorPos] === ':') {
				newPos++;
			}

			input.setSelectionRange(newPos, newPos);
		}
	};

	this.handleMacPaste = (event, input) => {
		event.preventDefault();
		const pasted = (event.clipboardData || window.clipboardData).getData('text');
		const clean = pasted.replace(/[^0-9A-Fa-f]/g, '').toUpperCase().substring(0, 12);
		let formatted = '';
		for (let i = 0; i < clean.length; i++) {
			if (i > 0 && i % 2 === 0) {
				formatted += ':';
			}
			formatted += clean[i];
		}
		input.value = formatted;
	};

	// Private: ONU activation logic with explicit context
	const doActivateOnu = (ctx, serviceId, mac) => {
		const input = ctx.querySelector('.getolt-new-mac-input');
		const btn = ctx.querySelector('.getolt-btn-activate');

		if (!serviceId) {
			mac = input.value.trim();
		}

		if (hasCyrillicLookalikes(mac)) {
			input.classList.add('getolt-is-invalid');
			showActivationStatus(ctx, 'MAC содержит кириллицу. Переключите на английскую раскладку.', 'error');
			return;
		}

		if (!isValidMac(mac)) {
			input.classList.add('getolt-is-invalid');
			showActivationStatus(ctx, 'Невалидный формат MAC. Используйте XX:XX:XX:XX:XX:XX', 'error');
			return;
		}
		input.classList.remove('getolt-is-invalid');

		mac = normalizeMac(mac);

		const processId = input.getAttribute('data-process-id');
		if (!processId) {
			showActivationStatus(ctx, 'Не найден ID процесса', 'error');
			return;
		}

		btn.disabled = true;
		btn.textContent = 'Обработка...';
		showActivationStatus(ctx, 'Отправка запроса...', 'processing');

		let url = '/user/plugin/getolt/getolt.do?method=activateOnu&processId=' + processId +
				  '&newMac=' + encodeURIComponent(mac);
		if (serviceId) {
			url += '&serviceId=' + serviceId;
		}

		debug('Activating ONU', url);

		$$.ajax.post(url, {})
			.done(function(response) {
				debug('Activation response', response);
				const result = response.data && response.data.data ? response.data.data : response.data;

				if (result && result.success) {
					let msg = 'MAC обновлён: ' + (result.oldMac || '-') + ' -> ' + result.newMac;
					if (result.sessionDropped) {
						msg += '\nСессия сброшена.';
					}
					showActivationStatus(ctx, msg, 'success');
					input.value = '';
					closeServiceModalIn(ctx);

					setTimeout(function() {
						reloadTab(btn);
					}, 2000);

				} else if (result && result.status === 'MULTIPLE_SERVICES') {
					showServiceModal(ctx, result.services, mac);
					hideActivationStatus(ctx);

				} else {
					const errorMsg = (result && result.message) ? result.message : (response.message || 'Ошибка активации');
					showActivationStatus(ctx, errorMsg, 'error');
				}

				btn.disabled = false;
				btn.textContent = 'Активировать';
			})
			.fail(function(xhr, status, error) {
				debug('Activation AJAX fail', status, error);
				showActivationStatus(ctx, 'Ошибка соединения с сервером', 'error');
				btn.disabled = false;
				btn.textContent = 'Активировать';
			});
	};

	// Public: activate ONU from JSP button click
	this.activateOnu = (btn) => {
		const ctx = containerOf(btn);
		doActivateOnu(ctx);
	};

	// === OLT Management page methods ===

	// Poll a single OLT from management page
	this.pollOlt = (oltId, btn) => {
		btn.disabled = true;
		const origText = btn.textContent;
		btn.textContent = '...';

		const url = '/user/plugin/getolt/manage.do?method=oltPoll&id=' + oltId;
		$$.ajax.post(url, {})
			.done((response) => {
				const result = response.data && response.data.data ? response.data.data : response.data;
				if (result && result.success) {
					btn.textContent = '\u2713';
					btn.style.color = '#28a745';
					setTimeout(() => {
						btn.textContent = origText;
						btn.style.color = '';
						btn.disabled = false;
						// Reload the list page
						const form = btn.closest('td').closest('tr').closest('table').previousElementSibling;
						if (form && form.tagName === 'FORM') {
							$$.ajax.load(form, $(form).parent());
						}
					}, 1500);
				} else {
					const msg = (result && result.message) ? result.message : 'Ошибка опроса';
					alert(msg);
					btn.textContent = origText;
					btn.disabled = false;
				}
			})
			.fail(() => {
				alert('Ошибка соединения');
				btn.textContent = origText;
				btn.disabled = false;
			});
	};

	// Toggle port list for an OLT
	this.togglePorts = (oltId, btn) => {
		const row = document.getElementById('getolt-ports-' + oltId);
		if (!row) return;

		if (row.style.display === 'none') {
			row.style.display = '';
			const container = row.querySelector('.getolt-ports-container');
			if (container && !container.hasChildNodes()) {
				container.innerHTML = '<div class="getolt-loading">Загрузка...</div>';
				const url = '/user/plugin/getolt/manage.do?method=portList&oltId=' + oltId;
				$$.ajax.load(url, $(container));
			}
		} else {
			row.style.display = 'none';
		}
	};

	// Import OLT list from external API
	this.importFromApi = (btn, uiid) => {
		btn.disabled = true;
		const origText = btn.textContent;
		btn.textContent = 'Загрузка...';

		const url = '/user/plugin/getolt/manage.do?method=importFromApi';
		$$.ajax.post(url, {})
			.done((response) => {
				const result = response.data && response.data.data ? response.data.data : response.data;
				if (result && result.success) {
					alert(result.message);
					const form = document.getElementById(uiid);
					if (form) {
						$$.ajax.load(form, $(form).parent());
					}
				} else {
					const msg = (result && result.message) ? result.message : 'Ошибка импорта';
					alert(msg);
				}
				btn.textContent = origText;
				btn.disabled = false;
			})
			.fail(() => {
				alert('Ошибка соединения');
				btn.textContent = origText;
				btn.disabled = false;
			});
	};

	// Get checked OLT IDs from the table
	const getCheckedOltIds = (uiid) => {
		const form = document.getElementById(uiid);
		if (!form) return [];
		const table = form.nextElementSibling;
		if (!table) return [];
		const checkboxes = table.querySelectorAll('.getolt-check-olt:checked');
		return Array.from(checkboxes).map(cb => cb.value);
	};

	// Toggle all checkboxes
	this.toggleCheckAll = (masterCb, uiid) => {
		const form = document.getElementById(uiid);
		if (!form) return;
		const table = form.nextElementSibling;
		if (!table) return;
		table.querySelectorAll('.getolt-check-olt').forEach(cb => {
			cb.checked = masterCb.checked;
		});
		this.updatePollBtn(uiid);
	};

	// Update poll button text based on checked count
	this.updatePollBtn = (uiid) => {
		const form = document.getElementById(uiid);
		if (!form) return;
		const btn = form.querySelector('.getolt-poll-btn');
		if (!btn) return;
		const checked = getCheckedOltIds(uiid);
		if (checked.length > 0) {
			btn.innerHTML = '&#8635; Обновить ' + checked.length;
		} else {
			btn.innerHTML = '&#8635; Обновить все';
		}
	};

	// Poll OLTs (all or selected via checkboxes)
	this.pollAll = (btn, uiid) => {
		const checkedIds = getCheckedOltIds(uiid);
		const eligibleCount = parseInt(btn.getAttribute('data-eligible-count'), 10) || 0;
		const count = checkedIds.length > 0 ? checkedIds.length : eligibleCount;

		if (count <= 0) {
			alert('Нет OLT для обновления');
			return;
		}

		const label = checkedIds.length > 0
			? 'Поставить в очередь обновления ' + checkedIds.length + ' выбранных OLT?'
			: 'Поставить в очередь обновления ' + eligibleCount + ' OLT?';

		if (!confirm(label)) return;

		btn.disabled = true;
		const origHtml = btn.innerHTML;
		btn.textContent = 'Запуск...';

		let url = '/user/plugin/getolt/manage.do?method=oltPollAll';
		if (checkedIds.length > 0) {
			url += '&ids=' + checkedIds.join(',');
		}

		$$.ajax.post(url, {})
			.done((response) => {
				const result = response.data && response.data.data ? response.data.data : response.data;
				if (result && result.success) {
					alert(result.message);
					const form = document.getElementById(uiid);
					if (form) {
						$$.ajax.load(form, $(form).parent());
					}
				} else {
					const msg = (result && result.message) ? result.message : 'Ошибка запуска';
					alert(msg);
				}
				btn.innerHTML = origHtml;
				btn.disabled = false;
			})
			.fail(() => {
				alert('Ошибка соединения');
				btn.innerHTML = origHtml;
				btn.disabled = false;
			});
	};

	// Refresh a single port from manage page
	this.refreshManagePort = (oltId, portNum, btn) => {
		btn.disabled = true;
		const origText = btn.textContent;
		btn.textContent = '...';

		const url = '/user/plugin/getolt/manage.do?method=portRefresh&oltId=' + oltId + '&portNum=' + portNum;
		$$.ajax.post(url, {})
			.done((response) => {
				const result = response.data && response.data.data ? response.data.data : response.data;
				if (result && result.success) {
					btn.textContent = '\u2713';
					btn.style.color = '#28a745';
					setTimeout(() => {
						btn.textContent = origText;
						btn.style.color = '';
						btn.disabled = false;
						// Reload ports table
						const portsContainer = btn.closest('.getolt-ports-container');
						if (portsContainer) {
							const oltRow = portsContainer.closest('.getolt-ports-row');
							if (oltRow) {
								const oltIdMatch = oltRow.id.match(/getolt-ports-(\d+)/);
								if (oltIdMatch) {
									const url = '/user/plugin/getolt/manage.do?method=portList&oltId=' + oltIdMatch[1];
									$$.ajax.load(url, $(portsContainer));
								}
							}
						}
					}, 1500);
				} else {
					const msg = (result && result.message) ? result.message : 'Ошибка обновления порта';
					alert(msg);
					btn.textContent = origText;
					btn.disabled = false;
				}
			})
			.fail(() => {
				alert('Ошибка соединения');
				btn.textContent = origText;
				btn.disabled = false;
			});
	};

	// Toggle ONU list for a port
	this.toggleOnus = (portId, btn) => {
		const row = document.getElementById('getolt-onus-' + portId);
		if (!row) return;

		if (row.style.display === 'none') {
			row.style.display = '';
			const container = row.querySelector('.getolt-onus-container');
			if (container && !container.hasChildNodes()) {
				container.innerHTML = '<div class="getolt-loading">Загрузка...</div>';
				const url = '/user/plugin/getolt/manage.do?method=onuList&portId=' + portId;
				$$.ajax.load(url, $(container));
			}
		} else {
			row.style.display = 'none';
		}
	};

	// Initialize on page content load
	this.init = (uiid) => {
		const ctx = document.getElementById(uiid);
		initRebootButtons(ctx);

		// Close modal on overlay click
		ctx.addEventListener('click', function(e) {
			if (e.target.classList.contains('getolt-service-modal-overlay')) {
				closeServiceModalIn(ctx);
			}
		});

		// Close modal on Escape key (register once globally)
		if (!escapeListenerAdded) {
			document.addEventListener('keydown', function(e) {
				if (e.key === 'Escape') {
					const visibleModal = document.querySelector('.getolt-service-modal-overlay.getolt-active');
					if (visibleModal) {
						visibleModal.classList.remove('getolt-active');
					}
				}
			});
			escapeListenerAdded = true;
		}
	};
};

/*
 * Plugin GetOLT — sub-namespace «Массовая авария» (ТЗ #11, Этап 1).
 * Вкладка процесса для аварийного диспетчера: выбор затронутых OLT/портов,
 * сохранение JSON в параметр процесса, саммари по выборке.
 */
$$.getolt.incident = new function() {
	const debug = $$.debug('getolt.incident');

	const containerOf = (el) => el.closest('.getolt-incident-container');

	const showError = (ctx, message) => {
		const banner = ctx.querySelector('.getolt-incident-banner-error');
		if (!banner) return;
		banner.textContent = message;
		banner.style.display = message ? '' : 'none';
		banner.classList.remove('getolt-incident-banner-success');
	};
	const showSuccess = (ctx, message, autoHideMs) => {
		const banner = ctx.querySelector('.getolt-incident-banner-error');
		if (!banner) return;
		banner.textContent = message;
		banner.style.display = '';
		banner.classList.add('getolt-incident-banner-success');
		if (autoHideMs) {
			setTimeout(() => {
				if (banner.textContent === message) {
					banner.style.display = 'none';
					banner.classList.remove('getolt-incident-banner-success');
				}
			}, autoHideMs);
		}
	};

	const ctxState = (ctx) => {
		if (!ctx._incidentState) ctx._incidentState = { olts: [], portsByIp: {} };
		return ctx._incidentState;
	};

	const readStoredSelection = (ctx) => {
		const node = ctx.querySelector('.getolt-incident-selection-data');
		if (!node) return [];
		const txt = (node.textContent || '').trim();
		if (!txt) return [];
		try {
			const parsed = JSON.parse(txt);
			return Array.isArray(parsed) ? parsed : [];
		} catch (e) {
			debug('cannot parse stored selection', e);
			return [];
		}
	};

	const collectSelection = (ctx) => {
		const result = [];
		ctx.querySelectorAll('.getolt-incident-olt-row').forEach(row => {
			const cb = row.querySelector('.getolt-incident-olt-check');
			if (!cb || !cb.checked) return;
			const ip = row.dataset.ip;
			const portsBlock = ctx.querySelector('.getolt-incident-ports[data-ip="' + CSS.escape(ip) + '"]');
			let allPorts = true;
			let ports = [];
			if (portsBlock) {
				const modeRadio = portsBlock.querySelector('input[type=radio][name="port-mode-' + ip + '"]:checked');
				allPorts = !modeRadio || modeRadio.value === 'all';
				if (!allPorts) {
					portsBlock.querySelectorAll('.getolt-incident-port-check:checked').forEach(pc => {
						ports.push(parseInt(pc.value, 10));
					});
				}
			}
			// Минимальный payload: ip + (опционально) ports.
			// Отсутствие ports => all ports. Поле billing/vendor не нужно для повтора —
			// подтянутся из API при открытии вкладки.
			const entry = { ip };
			if (!allPorts && ports.length > 0) entry.ports = ports;
			result.push(entry);
		});
		return result;
	};

	const parseExternalBody = (raw) => {
		if (!raw) return null;
		try {
			const obj = JSON.parse(raw);
			if (obj && obj.success === false) {
				return { error: (obj.error && obj.error.message) || 'External API error', data: null };
			}
			return { error: null, data: obj && 'data' in obj ? obj.data : obj };
		} catch (e) {
			return { error: 'Невалидный JSON от GetOLT API', data: null };
		}
	};

	const renderOltList = (ctx, olts) => {
		const wrap = ctx.querySelector('.getolt-incident-olts');
		if (!olts || olts.length === 0) {
			wrap.innerHTML = '<div class="getolt-incident-empty">OLT не найдены</div>';
			return;
		}
		const stored = readStoredSelection(ctx);
		const storedByIp = {};
		stored.forEach(s => storedByIp[s.ip] = s);

		const state = ctxState(ctx);
		state.portsByIp = {};

		wrap.innerHTML = olts.map(o => {
			const ip = o.ip || '';
			const billing = o.billing || o.operator || '';
			const vendor = o.vendor || '';
			const total = o.totalOnu != null ? o.totalOnu : (o.onuCount || 0);
			const active = o.activeOnu != null ? o.activeOnu : (o.onuActive || 0);
			state.portsByIp[ip] = Array.isArray(o.ports) ? o.ports : [];
			const sel = storedByIp[ip];
			const checked = sel ? 'checked' : '';
			return '<div class="getolt-incident-olt-row" data-ip="' + ip + '" data-billing="' + billing + '" data-vendor="' + vendor + '">'
				+ '<label><input type="checkbox" class="getolt-incident-olt-check" ' + checked + '> '
				+ '<span class="getolt-incident-olt-ip">' + ip + '</span> '
				+ '<span class="getolt-incident-olt-vendor">' + vendor + '</span> '
				+ '<span class="getolt-incident-olt-billing">' + billing + '</span> '
				+ '<span class="getolt-incident-olt-stats">' + total + ' ONU (' + active + ' online)</span>'
				+ '</label>'
				+ '<div class="getolt-incident-ports" data-ip="' + ip + '" style="display:none"></div>'
				+ '</div>';
		}).join('');

		// IP, которых нет в основном городском списке — догружаем точечно по IP.
		const presentIps = new Set();
		wrap.querySelectorAll('.getolt-incident-olt-row').forEach(r => presentIps.add(r.dataset.ip));
		const missing = stored.map(s => s.ip).filter(ip => ip && !presentIps.has(ip));
		if (missing.length) loadOltsByIps(ctx, missing);

		stored.forEach(s => {
			if (presentIps.has(s.ip)) showPortsFor(ctx, s.ip, s);
		});
		recalcSummary(ctx);
		// При первичной загрузке: если есть сохранённый выбор — переключаемся в режим
		// сводки сразу (даже если ещё ждём дозагрузку missing-IP — после неё recap
		// просто перерисуется с актуальными числами).
		if (stored.length > 0) {
			renderRecap(ctx);
			setEditorVisible(ctx, false);
		}
	};

	// Дозагрузка OLT по точечным IP (для восстановления выбора из других городов).
	const loadOltsByIps = (ctx, ips) => {
		const operator = ctx.dataset.operator || '';
		const url = '/user/plugin/getolt/massIncident.do?method=oltList'
			+ '&ips=' + encodeURIComponent(ips.join(','))
			+ '&operator=' + encodeURIComponent(operator);
		$$.ajax.post(url, {})
			.done(response => {
				const raw = response.data && response.data.body;
				const parsed = parseExternalBody(raw);
				if (!parsed || parsed.error) return;
				const olts = Array.isArray(parsed.data) ? parsed.data : (parsed.data && parsed.data.items) || [];
				appendOlts(ctx, olts);
				// После добавления в DOM — выставляем чекбоксы и восстанавливаем порты.
				const stored = readStoredSelection(ctx);
				const storedByIp = {};
				stored.forEach(s => storedByIp[s.ip] = s);
				ips.forEach(ip => {
					const row = ctx.querySelector('.getolt-incident-olt-row[data-ip="' + CSS.escape(ip) + '"]');
					if (!row) return;
					const cb = row.querySelector('.getolt-incident-olt-check');
					if (cb) cb.checked = true;
					if (storedByIp[ip]) showPortsFor(ctx, ip, storedByIp[ip]);
				});
				recalcSummary(ctx);
				// После дозагрузки — обновить и сводку (если она открыта в этот момент).
				renderRecap(ctx);
				if (stored.length > 0) setEditorVisible(ctx, false);
			});
	};

	const renderPorts = (ctx, ip, ports, stored) => {
		const block = ctx.querySelector('.getolt-incident-ports[data-ip="' + CSS.escape(ip) + '"]');
		if (!block) return;
		block.style.display = '';
		// Совместимость со старым форматом (allPorts=true/false): отсутствие ports
		// в новом формате => «все порты».
		const hasPortsList = stored && Array.isArray(stored.ports) && stored.ports.length > 0;
		const allChecked = !stored || (stored.allPorts !== false && !hasPortsList);
		const portsSet = new Set((stored && stored.ports) || []);
		const pickNum = (p, ...keys) => {
			for (const k of keys) {
				if (p[k] != null) return p[k];
			}
			return 0;
		};
		const signalBadge = (label, count, cls) => count > 0
			? '<span class="getolt-incident-sig ' + cls + '" title="' + label + '">' + count + '</span>'
			: '';
		const items = (ports || []).map(p => {
			const num = pickNum(p, 'portNumber', 'numPort', 'port', 'num');
			const onuTotal = pickNum(p, 'totalOnu', 'onuCount', 'numOnu', 'total', 'count');
			const onuActive = pickNum(p, 'activeOnu', 'onuActive', 'numActive', 'active', 'onlineCount');
			const good = pickNum(p, 'good');
			const medium = pickNum(p, 'medium');
			const poor = pickNum(p, 'poor');
			const overexposed = pickNum(p, 'overexposed');
			const noSignal = pickNum(p, 'noSignal');
			const c = (!allChecked && portsSet.has(num)) ? 'checked' : '';
			const sigParts = [
				signalBadge('Засвет (rx ≥ -15 dBm)', overexposed, 'getolt-incident-sig-over'),
				signalBadge('Хороший сигнал (> -25 dBm)', good, 'getolt-incident-sig-good'),
				signalBadge('Средний сигнал (-25…-30 dBm)', medium, 'getolt-incident-sig-medium'),
				signalBadge('Плохой сигнал (≤ -30 dBm)', poor, 'getolt-incident-sig-poor'),
				signalBadge('Нет данных о сигнале', noSignal, 'getolt-incident-sig-nosig'),
			].filter(Boolean).join(' ');
			return '<label class="getolt-incident-port-item">'
				+ '<input type="checkbox" class="getolt-incident-port-check" value="' + num + '" ' + c + '> '
				+ '<span class="getolt-incident-port-num">Порт ' + num + '</span>'
				+ '<span class="getolt-incident-port-onu">ONU: ' + onuActive + ' / ' + onuTotal + '</span>'
				+ (sigParts ? '<span class="getolt-incident-port-sig">' + sigParts + '</span>' : '')
				+ '</label>';
		}).join('');
		block.innerHTML =
			'<div class="getolt-incident-port-mode">'
			+ '<label><input type="radio" name="port-mode-' + ip + '" value="all" '
				+ (allChecked ? 'checked' : '') + ' onchange="$$.getolt.incident.togglePortMode(this)"> Все порты</label> '
			+ '<label><input type="radio" name="port-mode-' + ip + '" value="some" '
				+ (!allChecked ? 'checked' : '') + ' onchange="$$.getolt.incident.togglePortMode(this)"> Выбрать порты</label>'
			+ '</div>'
			+ '<div class="getolt-incident-port-list" style="' + (allChecked ? 'display:none' : '') + '">'
			+ items
			+ '</div>';
	};

	const showPortsFor = (ctx, ip, stored) => {
		const ports = ctxState(ctx).portsByIp[ip] || [];
		renderPorts(ctx, ip, ports, stored);
	};

	// --------- Режимы «Сводка» / «Редактор» ---------

	/**
	 * Переключить режим: показ сводки vs редактор.
	 * @param show true — показать редактор, скрыть сводку. false — наоборот.
	 */
	const setEditorVisible = (ctx, show) => {
		const editor = ctx.querySelector('.getolt-incident-editor');
		const recap  = ctx.querySelector('.getolt-incident-recap');
		const cancelBtn = ctx.querySelector('.getolt-incident-btn-cancel-edit');
		if (editor) editor.style.display = show ? '' : 'none';
		if (recap)  recap.style.display  = show ? 'none' : '';
		if (cancelBtn) cancelBtn.style.display = show ? '' : 'none';
	};

	this.toggleEditor = (btn, show) => {
		const ctx = containerOf(btn);
		if (!ctx) return;
		setEditorVisible(ctx, show);
		if (!show) renderRecap(ctx);
	};

	/** Рендерит сводку выбранных OLT по сохранённому JSON, используя данные state.portsByIp. */
	const renderRecap = (ctx) => {
		const list  = ctx.querySelector('.getolt-incident-recap-list');
		const total = ctx.querySelector('.getolt-incident-recap-total');
		if (!list || !total) return;

		const stored = readStoredSelection(ctx);
		if (stored.length === 0) {
			list.innerHTML = '';
			total.innerHTML = '<em>Затронутые OLT не выбраны</em>';
			return;
		}

		const state = ctxState(ctx);
		const num = (p, ...keys) => { for (const k of keys) if (p[k] != null) return p[k]; return 0; };

		let totOlt = stored.length, totPorts = 0, totOnline = 0, totAll = 0;

		const rowsHtml = stored.map(s => {
			const ports = state.portsByIp[s.ip] || [];
			const row = ctx.querySelector('.getolt-incident-olt-row[data-ip="' + CSS.escape(s.ip) + '"]');
			const billing = (row && row.dataset.billing) || '';
			const vendor  = (row && row.dataset.vendor)  || '';

			const portList = Array.isArray(s.ports) ? s.ports : [];
			const hasPortsList = portList.length > 0;
			const allPorts = !hasPortsList && (s.allPorts !== false);
			const set = new Set(portList);
			const chosen = allPorts ? ports : ports.filter(p => set.has(num(p, 'portNumber', 'numPort', 'port', 'num')));

			let online = 0, all = 0;
			chosen.forEach(p => {
				online += num(p, 'activeOnu', 'online');
				all    += num(p, 'totalOnu', 'onuCount');
			});
			totOnline += online; totAll += all;
			totPorts  += allPorts ? ports.length : portList.length;

			const portsLabel = allPorts
				? '<em>все порты</em> (' + ports.length + ')'
				: 'порт' + (portList.length > 1 ? 'ы' : '') + ' ' + portList.join(', ');

			return '<div class="getolt-incident-recap-row">'
				+ '<span class="getolt-incident-olt-ip">' + s.ip + '</span> '
				+ '<span class="getolt-incident-olt-vendor">' + vendor + '</span> '
				+ '<span class="getolt-incident-olt-billing">' + billing + '</span> '
				+ '<span class="getolt-incident-recap-ports">' + portsLabel + '</span>'
				+ '<span class="getolt-incident-olt-stats">' + online + ' online / ' + all + ' всего ONU</span>'
				+ '</div>';
		}).join('');

		list.innerHTML = rowsHtml;
		total.innerHTML = '<strong>Всего:</strong> ' + totOlt + ' OLT · '
			+ totPorts + ' порт' + (totPorts === 1 ? '' : (totPorts >= 2 && totPorts <= 4 ? 'а' : 'ов'))
			+ ' · <strong>Затронутых ONU:</strong> ' + totOnline + ' online / ' + totAll + ' всего';
	};

	const recalcSummary = (ctx) => {
		const sel = collectSelection(ctx);
		const summary = ctx.querySelector('.getolt-incident-summary');
		if (!summary) return;
		if (sel.length === 0) {
			summary.innerHTML = '<div class="getolt-incident-summary-empty">Выбор не сделан</div>';
			return;
		}

		const state = ctxState(ctx);
		let portCount = 0, allPortsCount = 0;
		let onuTotal = 0, onuOnline = 0;
		let good = 0, medium = 0, poor = 0, overexposed = 0, noSignal = 0;

		const num = (p, ...keys) => { for (const k of keys) if (p[k] != null) return p[k]; return 0; };

		sel.forEach(s => {
			const ports = state.portsByIp[s.ip] || [];
			// Совместимость двух форматов: новый (ports отсутствует ⇒ все порты)
			// и старый (есть allPorts).
			const portList = Array.isArray(s.ports) ? s.ports : [];
			const allPorts = portList.length === 0 && s.allPorts !== false;
			let chosen;
			if (allPorts) {
				allPortsCount++;
				chosen = ports;
			} else {
				portCount += portList.length;
				const set = new Set(portList);
				chosen = ports.filter(p => set.has(num(p, 'portNumber', 'numPort', 'port', 'num')));
			}
			chosen.forEach(p => {
				onuTotal += num(p, 'totalOnu', 'onuCount');
				onuOnline += num(p, 'activeOnu', 'online');
				good += num(p, 'good');
				medium += num(p, 'medium');
				poor += num(p, 'poor');
				overexposed += num(p, 'overexposed');
				noSignal += num(p, 'noSignal');
			});
		});

		const sigRow = (count, cls, title, hint) => count > 0
			? '<div class="getolt-incident-summary-row">'
				+ '<span class="getolt-incident-sig ' + cls + '">' + count + '</span>'
				+ '<span class="getolt-incident-summary-sig-label">' + title + '</span>'
				+ '<span class="getolt-incident-summary-sig-hint">' + hint + '</span>'
				+ '</div>'
			: '';

		const sigBlock = (overexposed + good + medium + poor + noSignal) > 0
			? '<div class="getolt-incident-summary-sig">'
				+ sigRow(overexposed, 'getolt-incident-sig-over',   'Засвет',  'rx ≥ -15 dBm')
				+ sigRow(good,        'getolt-incident-sig-good',   'Хороший', 'rx > -25 dBm')
				+ sigRow(medium,      'getolt-incident-sig-medium', 'Средний', '-25…-30 dBm')
				+ sigRow(poor,        'getolt-incident-sig-poor',   'Плохой',  'rx ≤ -30 dBm')
				+ sigRow(noSignal,    'getolt-incident-sig-nosig',  'Нет данных о сигнале', '—')
				+ '</div>'
			: '';

		summary.innerHTML =
			  '<div class="getolt-incident-summary-row"><strong>OLT:</strong> <span>' + sel.length + '</span></div>'
			+ '<div class="getolt-incident-summary-row"><strong>Конкретных портов:</strong> <span>' + portCount + '</span></div>'
			+ '<div class="getolt-incident-summary-row"><strong>OLT целиком:</strong> <span>' + allPortsCount + '</span></div>'
			+ '<div class="getolt-incident-summary-row"><strong>Затронутых ONU:</strong> <span>' + onuOnline + ' online / ' + onuTotal + ' всего</span></div>'
			+ sigBlock;
	};

	this.init = (uiid) => {
		const ctx = document.getElementById(uiid);
		if (!ctx) return;
		debug('init', uiid);

		// Если есть сохранённый выбор — открываем сводку, редактор скрыт.
		// Если выбор пуст — сразу редактирование.
		const initialStored = readStoredSelection(ctx);
		setEditorVisible(ctx, initialStored.length === 0);
		if (initialStored.length > 0) {
			// Черновая сводка — пока без чисел ONU, заполнятся после загрузки.
			renderRecap(ctx);
		}

		ctx.addEventListener('input', (e) => {
			if (e.target.classList.contains('getolt-incident-add-location-input')) {
				this.suggestCities(e.target);
			}
		});

		ctx.addEventListener('change', (e) => {
			if (e.target.classList.contains('getolt-incident-city-check')) {
				const block = e.target.closest('.getolt-incident-city-block');
				const wrap = block.querySelector('.getolt-incident-city-olts');
				if (e.target.checked) {
					expandCityOlts(ctx, block, true);
				} else {
					// Только снимаем все отметки OLT внутри города; список оставляем
					// раскрытым, чтобы пользователь мог дочекать вручную.
					if (wrap.dataset.loaded === '1') setOltChecksInBlock(wrap, false);
				}
				return;
			}
			if (e.target.classList.contains('getolt-incident-olt-check')) {
				const row = e.target.closest('.getolt-incident-olt-row');
				const ip = row.dataset.ip;
				if (e.target.checked) {
					showPortsFor(ctx, ip, null);
				} else {
					const block = row.querySelector('.getolt-incident-ports');
					if (block) { block.style.display = 'none'; block.innerHTML = ''; }
				}
				recalcSummary(ctx);
			} else if (e.target.classList.contains('getolt-incident-port-check')) {
				recalcSummary(ctx);
			}
		});

		this.reloadOlts(ctx);
	};

	this.reloadOlts = (elOrCtx) => {
		const ctx = elOrCtx.classList && elOrCtx.classList.contains('getolt-incident-container')
			? elOrCtx : containerOf(elOrCtx);
		if (!ctx) return;
		showError(ctx, '');
		const wrap = ctx.querySelector('.getolt-incident-olts');
		wrap.innerHTML = '<div class="getolt-incident-loading">Загрузка списка OLT…</div>';
		const city = ctx.dataset.city || '';
		const operator = ctx.dataset.operator || '';
		const url = '/user/plugin/getolt/massIncident.do?method=oltList&city=' + encodeURIComponent(city)
			+ '&operator=' + encodeURIComponent(operator);
		$$.ajax.post(url, {})
			.done(response => {
				const raw = response.data && response.data.body;
				const err = response.data && response.data.error;
				if (err) { showError(ctx, err); wrap.innerHTML = ''; return; }
				const parsed = parseExternalBody(raw);
				if (!parsed || parsed.error) {
					showError(ctx, parsed ? parsed.error : 'Ошибка ответа GetOLT');
					wrap.innerHTML = '';
					return;
				}
				const olts = Array.isArray(parsed.data) ? parsed.data : (parsed.data && parsed.data.items) || [];
				ctxState(ctx).olts = olts;
				renderOltList(ctx, olts);
			})
			.fail(() => {
				showError(ctx, 'Не удалось загрузить список OLT');
				wrap.innerHTML = '';
			});
	};

	// Раскрыть/свернуть форму поиска OLT другой локации.
	this.toggleAddLocation = (btn) => {
		const ctx = containerOf(btn);
		if (!ctx) return;
		const block = ctx.querySelector('.getolt-incident-add-location');
		const form = block.querySelector('.getolt-incident-add-location-form');
		const open = form.style.display === 'none';
		form.style.display = open ? '' : 'none';
		block.querySelector('.getolt-incident-btn-add-location').style.display = open ? 'none' : '';
		if (open) {
			const input = form.querySelector('.getolt-incident-add-location-input');
			input.value = '';
			input.focus();
			form.querySelector('.getolt-incident-add-location-suggestions').innerHTML
				= '<div class="getolt-incident-add-location-hint">Введите минимум 3 символа…</div>';
			form.querySelector('.getolt-incident-add-location-actions').style.display = 'none';
		}
	};

	// Debounced подсказки городов: после 3 символов показываем чекбоксы.
	const cityDebounce = new WeakMap();

	const renderCitySuggestions = (ctx, cities) => {
		const wrap = ctx.querySelector('.getolt-incident-add-location-suggestions');
		const actions = ctx.querySelector('.getolt-incident-add-location-actions');
		if (!cities || cities.length === 0) {
			wrap.innerHTML = '<div class="getolt-incident-add-location-empty">Городов не найдено</div>';
			actions.style.display = 'none';
			return;
		}
		wrap.innerHTML = cities.map(c => {
			const safe = c.replace(/"/g, '&quot;');
			// Не используем <label>: клик по тексту не должен переключать чекбокс.
			return '<div class="getolt-incident-city-block" data-city="' + safe + '">'
				+ '<div class="getolt-incident-city-suggestion">'
				+ '<input type="checkbox" class="getolt-incident-city-check" value="' + safe + '" title="Отметить все OLT этого города"> '
				+ '<span class="getolt-incident-city-name" onclick="$$.getolt.incident.toggleCityExpand(this)" title="Показать/скрыть OLT">' + c + '</span>'
				+ '</div>'
				+ '<div class="getolt-incident-city-olts" style="display:none"></div>'
				+ '</div>';
		}).join('');
		actions.style.display = '';
	};

	/**
	 * Раскрывает блок OLT города. Если данных нет — грузит, затем callback.
	 * @param autoCheckAll true — после загрузки отметить все доступные чекбоксы OLT.
	 */
	const expandCityOlts = (ctx, block, autoCheckAll) => {
		const wrap = block.querySelector('.getolt-incident-city-olts');
		wrap.style.display = '';
		// Если уже было загружено — не запрашиваем второй раз.
		if (wrap.dataset.loaded === '1') {
			if (autoCheckAll) setOltChecksInBlock(wrap, true);
			return;
		}
		const city = block.dataset.city;
		const operator = ctx.dataset.operator || '';
		wrap.innerHTML = '<div class="getolt-incident-add-location-hint">Загрузка OLT…</div>';
		const url = '/user/plugin/getolt/massIncident.do?method=oltList&cities=' + encodeURIComponent(city)
			+ '&operator=' + encodeURIComponent(operator);
		$$.ajax.post(url, {})
			.done(response => {
				const raw = response.data && response.data.body;
				const parsed = parseExternalBody(raw);
				if (!parsed || parsed.error) {
					wrap.innerHTML = '<div class="getolt-incident-add-location-hint">Ошибка ответа</div>';
					return;
				}
				const olts = Array.isArray(parsed.data) ? parsed.data : (parsed.data && parsed.data.items) || [];
				renderCityOlts(ctx, wrap, olts, autoCheckAll);
				wrap.dataset.loaded = '1';
			})
			.fail(() => {
				wrap.innerHTML = '<div class="getolt-incident-add-location-hint">Не удалось загрузить</div>';
			});
	};

	const collapseCityOlts = (block) => {
		const wrap = block.querySelector('.getolt-incident-city-olts');
		wrap.style.display = 'none';
	};

	const setOltChecksInBlock = (wrap, value) => {
		wrap.querySelectorAll('.getolt-incident-suggested-olt-check:not(:disabled)').forEach(cb => {
			cb.checked = value;
		});
	};

	const renderCityOlts = (ctx, wrap, olts, autoCheckAll) => {
		if (!olts || olts.length === 0) {
			wrap.innerHTML = '<div class="getolt-incident-add-location-empty">OLT в этом городе нет</div>';
			return;
		}
		// Уже добавленные в основной список IP — не предлагать.
		const alreadyShown = new Set();
		ctx.querySelectorAll('.getolt-incident-olt-row').forEach(r => alreadyShown.add(r.dataset.ip));

		// Кешируем сами объекты OLT, чтобы при добавлении в основной список были порты.
		const state = ctxState(ctx);
		state.suggestedByIp = state.suggestedByIp || {};

		const items = olts.map(o => {
			const ip = o.ip || '';
			state.suggestedByIp[ip] = o;
			const billing = o.billing || o.operator || '';
			const vendor = o.vendor || '';
			const total = o.totalOnu != null ? o.totalOnu : (o.onuCount || 0);
			const active = o.activeOnu != null ? o.activeOnu : (o.onuActive || 0);
			const dup = alreadyShown.has(ip);
			const checked = !dup && autoCheckAll ? ' checked' : '';
			return '<label class="getolt-incident-suggested-olt' + (dup ? ' getolt-incident-suggested-dup' : '') + '">'
				+ '<input type="checkbox" class="getolt-incident-suggested-olt-check" value="' + ip + '"'
					+ (dup ? ' disabled' : checked) + '> '
				+ '<span class="getolt-incident-olt-ip">' + ip + '</span> '
				+ '<span class="getolt-incident-olt-vendor">' + vendor + '</span> '
				+ '<span class="getolt-incident-olt-billing">' + billing + '</span> '
				+ '<span class="getolt-incident-olt-stats">' + total + ' ONU (' + active + ' online)' + (dup ? ' · уже добавлен' : '') + '</span>'
				+ '</label>';
		}).join('');
		wrap.innerHTML = items;
	};

	// Клик по тексту города — toggle сворачивания списка OLT, без изменения отметок.
	this.toggleCityExpand = (span) => {
		const ctx = containerOf(span);
		if (!ctx) return;
		const block = span.closest('.getolt-incident-city-block');
		const wrap = block.querySelector('.getolt-incident-city-olts');
		if (wrap.style.display === 'none') {
			expandCityOlts(ctx, block, false);
		} else {
			collapseCityOlts(block);
		}
	};

	this.suggestCities = (input) => {
		const ctx = containerOf(input);
		if (!ctx) return;
		const wrap = ctx.querySelector('.getolt-incident-add-location-suggestions');
		const actions = ctx.querySelector('.getolt-incident-add-location-actions');
		const query = (input.value || '').trim();
		if (query.length < 3) {
			wrap.innerHTML = '<div class="getolt-incident-add-location-hint">Введите минимум 3 символа…</div>';
			actions.style.display = 'none';
			return;
		}
		clearTimeout(cityDebounce.get(input));
		const tid = setTimeout(() => {
			const operator = ctx.dataset.operator || '';
			const url = '/user/plugin/getolt/massIncident.do?method=cities&query=' + encodeURIComponent(query)
				+ '&operator=' + encodeURIComponent(operator);
			wrap.innerHTML = '<div class="getolt-incident-add-location-hint">Поиск…</div>';
			$$.ajax.post(url, {})
				.done(response => {
					const cities = (response.data && response.data.cities) || [];
					renderCitySuggestions(ctx, cities);
				})
				.fail(() => {
					wrap.innerHTML = '<div class="getolt-incident-add-location-hint">Ошибка загрузки городов</div>';
					actions.style.display = 'none';
				});
		}, 300);
		cityDebounce.set(input, tid);
	};

	this.addSelectedCities = (btn) => {
		const ctx = containerOf(btn);
		if (!ctx) return;
		const state = ctxState(ctx);
		const suggested = state.suggestedByIp || {};
		const ipsChecked = ctx.querySelectorAll('.getolt-incident-suggested-olt-check:checked:not(:disabled)');
		if (ipsChecked.length === 0) {
			showError(ctx, 'Выберите хотя бы один OLT');
			return;
		}
		const olts = [];
		ipsChecked.forEach(cb => {
			const o = suggested[cb.value];
			if (o) olts.push(o);
		});
		const added = appendOlts(ctx, olts);
		showError(ctx, added === 0 ? 'Все выбранные OLT уже в списке' : '');
		if (added > 0) this.toggleAddLocation(btn);
	};

	// Возвращает количество добавленных OLT (не считая дубликатов по IP).
	const appendOlts = (ctx, olts) => {
		const wrap = ctx.querySelector('.getolt-incident-olts');
		const state = ctxState(ctx);
		const existing = new Set();
		wrap.querySelectorAll('.getolt-incident-olt-row').forEach(r => existing.add(r.dataset.ip));
		const empty = wrap.querySelector('.getolt-incident-empty');
		if (empty) empty.remove();

		let added = 0;
		olts.forEach(o => {
			const ip = o.ip || '';
			if (!ip || existing.has(ip)) return;
			state.portsByIp[ip] = Array.isArray(o.ports) ? o.ports : [];
			const billing = o.billing || o.operator || '';
			const vendor = o.vendor || '';
			const total = o.totalOnu != null ? o.totalOnu : (o.onuCount || 0);
			const active = o.activeOnu != null ? o.activeOnu : (o.onuActive || 0);
			const html = '<div class="getolt-incident-olt-row" data-ip="' + ip + '" data-billing="' + billing + '" data-vendor="' + vendor + '">'
				+ '<label><input type="checkbox" class="getolt-incident-olt-check"> '
				+ '<span class="getolt-incident-olt-ip">' + ip + '</span> '
				+ '<span class="getolt-incident-olt-vendor">' + vendor + '</span> '
				+ '<span class="getolt-incident-olt-billing">' + billing + '</span> '
				+ '<span class="getolt-incident-olt-stats">' + total + ' ONU (' + active + ' online)</span>'
				+ '</label>'
				+ '<div class="getolt-incident-ports" data-ip="' + ip + '" style="display:none"></div>'
				+ '</div>';
			wrap.insertAdjacentHTML('beforeend', html);
			added++;
		});
		return added;
	};

	this.togglePortMode = (radio) => {
		const ctx = containerOf(radio);
		if (!ctx) return;
		const portsBlock = radio.closest('.getolt-incident-ports');
		const list = portsBlock.querySelector('.getolt-incident-port-list');
		if (list) list.style.display = (radio.value === 'all') ? 'none' : '';
		recalcSummary(ctx);
	};

	this.save = (btn) => {
		const ctx = containerOf(btn);
		if (!ctx) return;
		const selection = collectSelection(ctx);
		const processId = ctx.dataset.processId;
		btn.disabled = true;
		const url = '/user/plugin/getolt/massIncident.do?method=save&processId=' + encodeURIComponent(processId)
			+ '&selection=' + encodeURIComponent(JSON.stringify(selection));
		const origText = btn.textContent;
		$$.ajax.post(url, {})
			.done(response => {
				btn.disabled = false;
				const err = response.data && response.data.error;
				if (err) { showError(ctx, err); return; }
				const node = ctx.querySelector('.getolt-incident-selection-data');
				if (node) node.textContent = JSON.stringify(selection);
				debug('saved', selection.length, 'OLT entries');
				recalcSummary(ctx);
				showSuccess(ctx, '✓ Сохранено: ' + selection.length + ' OLT', 3000);
				btn.textContent = '✓ Сохранено';
				setTimeout(() => { btn.textContent = origText; }, 2000);
				// После успешного сохранения — переход в режим сводки.
				renderRecap(ctx);
				if (selection.length > 0) setEditorVisible(ctx, false);
			})
			.fail(() => {
				btn.disabled = false;
				showError(ctx, 'Не удалось сохранить выбор');
			});
	};
};
