/**
 * KundenKarte Graph-Ansicht (Cytoscape.js)
 * Hierarchisches Top-Down Layout (dagre)
 * Gebäude-Typen als Container, Geräte darin, Kabel als Verbindungen
 * Keine Pfeile - Stromleitungen haben keine Richtung
 *
 * Copyright (C) 2026 Alles Watt lauft
 */
(function() {
	'use strict';

	window.KundenKarte = window.KundenKarte || {};

	// FontAwesome 5 Unicode-Mapping für Cytoscape-Labels
	var FA_ICONS = {
		'fa-home': '\uf015',
		'fa-building': '\uf1ad',
		'fa-warehouse': '\uf494',
		'fa-compass': '\uf14e',
		'fa-utensils': '\uf2e7',
		'fa-bed': '\uf236',
		'fa-bath': '\uf2cd',
		'fa-car': '\uf1b9',
		'fa-car-side': '\uf5e4',
		'fa-tree': '\uf1bb',
		'fa-leaf': '\uf06c',
		'fa-sun': '\uf185',
		'fa-child': '\uf1ae',
		'fa-desktop': '\uf108',
		'fa-server': '\uf233',
		'fa-cogs': '\uf085',
		'fa-tools': '\uf7d9',
		'fa-box': '\uf466',
		'fa-door-open': '\uf52b',
		'fa-door-closed': '\uf52a',
		'fa-square': '\uf0c8',
		'fa-charging-station': '\uf5e7',
		'fa-database': '\uf1c0',
		'fa-fire': '\uf06d',
		'fa-solar-panel': '\uf5ba',
		'fa-tachometer-alt': '\uf3fd',
		'fa-th-list': '\uf00b',
		'fa-bolt': '\uf0e7',
		'fa-plus-square': '\uf0fe',
		'fa-level-down-alt': '\uf3be',
		'fa-level-up-alt': '\uf3bf',
		'fa-layer-group': '\uf5fd',
		'fa-arrows-alt-h': '\uf337',
		'fa-tshirt': '\uf553',
		'fa-mountain': '\uf6fc',
		'fa-horse': '\uf6f0',
		'fa-toilet': '\uf7d8',
		'fa-couch': '\uf4b8',
		'fa-plug': '\uf1e6',
		'fa-sitemap': '\uf0e8'
	};

	KundenKarte.Graph = {
		cy: null,
		containerId: 'kundenkarte-graph-container',
		ajaxUrl: '',
		saveUrl: '',
		moduleUrl: '',
		pageUrl: '',
		systemId: 0,
		socId: 0,
		contactId: 0,
		isContact: false,
		canEdit: false,
		canDelete: false,
		tooltipEl: null,
		contextMenuEl: null,
		contextMenuNodeId: null,
		wheelZoomEnabled: false,
		hasPositions: false,
		editMode: false,
		_savedPositions: {},
		_saveTimer: null,
		_dirtyNodes: {},
		_viewportTimer: null,

		/**
		 * Initialisierung
		 */
		init: function(config) {
			this.socId = config.socId || 0;
			this.contactId = config.contactId || 0;
			this.isContact = config.isContact || false;

			this.createTooltipElement();
			this.initContextMenu();
			this.bindZoomButtons();
			this.bindSearch();
			this.loadGraphData();
		},

		/**
		 * FontAwesome-Klasse in Unicode-Zeichen umwandeln
		 */
		getIconChar: function(picto) {
			if (!picto) return '';
			var parts = picto.split(' ');
			for (var i = 0; i < parts.length; i++) {
				if (parts[i].indexOf('fa-') === 0 && FA_ICONS[parts[i]]) {
					return FA_ICONS[parts[i]];
				}
			}
			return '';
		},

		/**
		 * Tooltip-Element erzeugen
		 */
		createTooltipElement: function() {
			this.tooltipEl = document.createElement('div');
			this.tooltipEl.className = 'kundenkarte-graph-tooltip';
			this.tooltipEl.style.display = 'none';
			document.body.appendChild(this.tooltipEl);
		},

		/**
		 * Daten vom Server laden
		 */
		loadGraphData: function() {
			var self = this;
			var url = this.ajaxUrl + '?socid=' + this.socId;
			if (this.contactId > 0) {
				url += '&contactid=' + this.contactId;
			}

			$.ajax({
				url: url,
				type: 'GET',
				dataType: 'json',
				success: function(response) {
					$('#' + self.containerId + ' .kundenkarte-graph-loading').remove();

					if (response.success) {
						if (response.elements.nodes.length === 0) {
							self.showEmpty();
						} else {
							self.hasPositions = !!response.has_positions;
							try {
								self.renderGraph(response.elements);
							} catch(e) {
								console.error('KundenKarte Graph Fehler:', e);
							}
							// Legende IMMER rendern (auch bei Graph-Fehler)
							self.renderLegend(response.cable_types || []);
						}
					} else {
						if (typeof KundenKarte.showError === 'function') {
							KundenKarte.showError('Fehler', response.error || 'Daten konnten nicht geladen werden');
						}
					}
				},
				error: function() {
					$('#' + self.containerId + ' .kundenkarte-graph-loading').remove();
					if (typeof KundenKarte.showError === 'function') {
						KundenKarte.showError('Fehler', 'Netzwerkfehler beim Laden der Graph-Daten');
					}
				}
			});
		},

		/**
		 * Leeren Zustand anzeigen
		 */
		showEmpty: function() {
			$('#' + this.containerId).html(
				'<div class="kundenkarte-graph-empty">' +
				'<i class="fa fa-sitemap"></i>' +
				'<span>Keine Elemente vorhanden</span>' +
				'</div>'
			);
		},

		/**
		 * Dynamische Legende rendern (Gebäude + Geräte + Kabeltypen)
		 */
		renderLegend: function(cableTypes) {
			var $legend = $('#kundenkarte-graph-legend');
			if (!$legend.length) return;

			var html = '';
			// Feste Einträge: Raum + Gerät
			html += '<span class="kundenkarte-graph-legend-item">';
			html += '<span class="kundenkarte-graph-legend-box building"></span> Raum/Gebäude</span>';
			html += '<span class="kundenkarte-graph-legend-item">';
			html += '<span class="kundenkarte-graph-legend-box device"></span> Gerät</span>';

			// Dynamische Kabeltypen mit Farben
			if (cableTypes && cableTypes.length > 0) {
				for (var i = 0; i < cableTypes.length; i++) {
					var ct = cableTypes[i];
					html += '<span class="kundenkarte-graph-legend-item">';
					html += '<span class="kundenkarte-graph-legend-line" style="background:' + ct.color + ';"></span> ';
					html += this.escapeHtml(ct.label) + '</span>';
				}
			}

			// Durchgeschleift (immer anzeigen)
			html += '<span class="kundenkarte-graph-legend-item">';
			html += '<span class="kundenkarte-graph-legend-line passthrough"></span> Durchgeschleift</span>';

			// Hierarchie (Eltern→Kind Beziehung zwischen Geräten)
			html += '<span class="kundenkarte-graph-legend-item">';
			html += '<span class="kundenkarte-graph-legend-line hierarchy"></span> Hierarchie</span>';

			$legend.html(html);
		},

		/**
		 * Rastergröße für Snap-to-Grid (unsichtbar)
		 */
		gridSize: 20,

		/**
		 * Maximaler Zoom beim Einpassen (verhindert zu starkes Reinzoomen)
		 */
		maxFitZoom: 0.85,

		/**
		 * LocalStorage-Key für Viewport-State
		 */
		_viewportKey: function() {
			return 'kundenkarte_graph_vp_' + this.socId + '_' + this.contactId;
		},

		/**
		 * Viewport (Zoom + Pan) im localStorage speichern
		 */
		saveViewport: function() {
			if (!this.cy) return;
			try {
				localStorage.setItem(this._viewportKey(), JSON.stringify({
					zoom: this.cy.zoom(),
					pan: this.cy.pan()
				}));
			} catch(e) { /* localStorage voll/nicht verfügbar */ }
		},

		/**
		 * Viewport aus localStorage wiederherstellen
		 * @return {boolean} true wenn wiederhergestellt
		 */
		restoreViewport: function() {
			if (!this.cy) return false;
			try {
				var saved = localStorage.getItem(this._viewportKey());
				if (saved) {
					var vp = JSON.parse(saved);
					this.cy.viewport({ zoom: vp.zoom, pan: vp.pan });
					return true;
				}
			} catch(e) { /* ungültige Daten */ }
			return false;
		},

		/**
		 * Graph rendern
		 */
		renderGraph: function(elements) {
			var self = this;

			// Labels zusammenbauen: Name + Klammer-Felder + Badge-Felder
			var cyElements = [];
			if (elements.nodes) {
				elements.nodes.forEach(function(n) {
					var icon = self.getIconChar(n.data.type_picto);
					var namePart = icon ? (icon + '  ' + n.data.label) : n.data.label;

					if (n.data.is_building) {
						// Gebäude: Icon + Name + evtl. Klammer-Felder
						var parens = [];
						if (n.data.fields) {
							for (var i = 0; i < n.data.fields.length; i++) {
								var f = n.data.fields[i];
								if (f.display === 'parentheses' && f.value) {
									parens.push(f.value);
								}
							}
						}
						n.data.display_label = parens.length > 0
							? namePart + '  (' + parens.join(', ') + ')'
							: namePart;
					} else {
						// Gerät: Name (+ Klammer) + Typ + Trennlinie + Badge-Werte mit Feldbezeichnung
						var parenParts = [];
						var badgeLines = [];

						if (n.data.fields) {
							for (var i = 0; i < n.data.fields.length; i++) {
								var f = n.data.fields[i];
								var v = f.value;
								if (!v || v === '') continue;
								if (f.type === 'checkbox' && (v === '1' || v === 'true')) v = '\u2713';

								if (f.display === 'parentheses') {
									parenParts.push(v);
								} else if (f.display === 'badge') {
									// Feldname: Wert
									badgeLines.push(f.label + ':  ' + v);
								}
							}
						}

						var lines = [];
						// Zeile 1: Name + Klammer-Werte
						if (parenParts.length > 0) {
							lines.push(namePart + '  (' + parenParts.join(', ') + ')');
						} else {
							lines.push(namePart);
						}
						// Trennlinie + Typ + Badge-Werte
						var hasDetails = (n.data.type_label || badgeLines.length > 0);
						if (hasDetails) {
							lines.push('────────────────────');
						}
						// Typ-Bezeichnung immer unter dem Strich
						if (n.data.type_label) {
							lines.push('Typ:  ' + n.data.type_label);
						}
						// Badge-Werte mit Feldbezeichnung
						for (var j = 0; j < badgeLines.length; j++) {
							lines.push(badgeLines[j]);
						}

						// Datei-Indikatoren
						var fileInfo = [];
						if (n.data.image_count > 0) fileInfo.push('\ud83d\uddbc ' + n.data.image_count);
						if (n.data.doc_count > 0) fileInfo.push('\ud83d\udcc4 ' + n.data.doc_count);
						if (fileInfo.length > 0) {
							if (badgeLines.length === 0) lines.push('────────────────────');
							lines.push(fileInfo.join('  \u00b7  '));
						}

						n.data.display_label = lines.join('\n');
					}

					cyElements.push(n);
				});
			}
			if (elements.edges) {
				elements.edges.forEach(function(e) { cyElements.push(e); });
			}

			// Einpassen, aber nicht über maxFitZoom hinaus zoomen
			var fitWithLimit = function(cy) {
				cy.fit(undefined, 50);
				if (cy.zoom() > self.maxFitZoom) {
					cy.zoom(self.maxFitZoom);
					cy.center();
				}
			};

			// Container-Höhe an Inhalt anpassen
			var autoResizeContainer = function(cy) {
				var bb = cy.elements().boundingBox();
				if (!bb || bb.w === 0) return;
				// Benötigte Höhe bei aktuellem Zoom + Padding
				var neededHeight = (bb.h * cy.zoom()) + 100;
				var container = document.getElementById(self.containerId);
				var minH = 300;
				var maxH = window.innerHeight * 0.8;
				var newH = Math.max(minH, Math.min(maxH, neededHeight));
				if (Math.abs(newH - container.clientHeight) > 20) {
					container.style.height = Math.round(newH) + 'px';
					cy.resize();
				}
			};

			// Cytoscape-Instanz erstellen (ohne Layout - wird separat gestartet)
			this.cy = cytoscape({
				container: document.getElementById(this.containerId),
				elements: cyElements,
				style: this.getStylesheet(),
				minZoom: 0.15,
				maxZoom: 4,
				userZoomingEnabled: false,
				userPanningEnabled: true,
				boxSelectionEnabled: false,
				autoungrabify: true,
				layout: { name: 'preset' }
			});

			// dagre-Layout NACH Instanz-Erstellung starten (self.cy ist jetzt gesetzt)
			this.cy.layout({
				name: 'dagre',
				rankDir: 'TB',
				nodeSep: 50,
				rankSep: 80,
				fit: false,
				stop: function() {
					if (self.hasPositions) {
						// Gespeicherte Positionen anwenden
						self.cy.nodes().forEach(function(node) {
							var d = node.data();
							if (d.graph_x !== null && d.graph_y !== null) {
								node.position({ x: d.graph_x, y: d.graph_y });
							}
						});
						// Container an Inhalt anpassen, dann Viewport wiederherstellen
						autoResizeContainer(self.cy);
						if (!self.restoreViewport()) {
							fitWithLimit(self.cy);
						}
					} else {
						// Kein gespeichertes Layout: dagre-Ergebnis einpassen
						autoResizeContainer(self.cy);
						fitWithLimit(self.cy);
					}
				}
			}).run();

			this.bindGraphEvents();
		},

		/**
		 * Dolibarr CSS-Variablen auslesen (Fallback für nicht-gesetzte Werte)
		 */
		getCssVar: function(name, fallback) {
			try {
				var val = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
				return val || fallback;
			} catch(e) {
				return fallback;
			}
		},

		/**
		 * Cytoscape Stylesheet
		 */
		getStylesheet: function() {
			var faFont = '"Font Awesome 5 Free", "FontAwesome", sans-serif';
			// Dolibarr-Schriftfarbe verwenden
			var textColor = this.getCssVar('--colortext', '#dcdcdc');

			return [
				// Räume/Gebäude (Compound-Container)
				{
					selector: '.building-node',
					style: {
						'shape': 'roundrectangle',
						'background-color': '#2a2b2d',
						'background-opacity': 1,
						'border-width': 2,
						'border-color': function(node) { return node.data('type_color') || '#4390dc'; },
						'border-style': 'dashed',
						'padding': '25px',
						'label': 'data(display_label)',
						'font-family': faFont,
						'font-weight': 'bold',
						'text-valign': 'top',
						'text-halign': 'center',
						'font-size': '13px',
						'color': textColor,
						'text-margin-y': -8,
						'text-background-color': '#2a2b2d',
						'text-background-opacity': 0.95,
						'text-background-padding': '4px',
						'text-background-shape': 'roundrectangle',
						'min-width': '120px',
						'min-height': '60px'
					}
				},
				// Geräte - Karten-Design mit Feldwerten (kompakt)
				{
					selector: '.device-node',
					style: {
						'shape': 'roundrectangle',
						'width': 'label',
						'height': 'label',
						'padding': '12px',
						'background-color': '#2d3d35',
						'background-opacity': 0.95,
						'border-width': 2,
						'border-color': function(node) { return node.data('type_color') || '#5a9a6a'; },
						'label': 'data(display_label)',
						'font-family': faFont,
						'font-weight': 900,
						'text-valign': 'center',
						'text-halign': 'center',
						'text-justification': 'left',
						'font-size': '11px',
						'color': textColor,
						'text-wrap': 'wrap',
						'text-max-width': '240px',
						'line-height': 1.5
					}
				},
				// Kabel - Farbe aus medium_color, Fallback grün, rechtwinklig
				{
					selector: '.cable-edge',
					style: {
						'width': 3,
						'line-color': function(edge) {
							return edge.data('medium_color') || '#5a8a5a';
						},
						'target-arrow-shape': 'none',
						'source-arrow-shape': 'none',
						'curve-style': 'taxi',
						'taxi-direction': 'downward',
						'taxi-turn': '40px',
						'label': 'data(label)',
						'font-size': '9px',
						'color': '#8a9aa8',
						'text-rotation': 'autorotate',
						'text-background-color': '#1a2030',
						'text-background-opacity': 0.85,
						'text-background-padding': '3px',
						'text-background-shape': 'roundrectangle'
					}
				},
				// Durchgeschleifte Leitungen - gestrichelt
				{
					selector: '.passthrough-edge',
					style: {
						'width': 1,
						'line-color': '#505860',
						'line-style': 'dashed',
						'target-arrow-shape': 'none',
						'source-arrow-shape': 'none',
						'curve-style': 'bezier'
					}
				},
				// Hierarchie-Kanten (Gerät→Gerät Eltern-Kind, rechtwinklig)
				{
					selector: '.hierarchy-edge',
					style: {
						'width': 1.5,
						'line-color': '#6a7a8a',
						'line-style': 'dotted',
						'target-arrow-shape': 'none',
						'source-arrow-shape': 'none',
						'curve-style': 'taxi',
						'taxi-direction': 'downward',
						'taxi-turn': '30px',
						'opacity': 0.6
					}
				},
				// Hover
				{
					selector: 'node:active',
					style: {
						'overlay-opacity': 0.1,
						'overlay-color': '#7ab0d4'
					}
				},
				// Selektiert
				{
					selector: ':selected',
					style: {
						'border-color': '#d4944a',
						'border-width': 3
					}
				},
				// Suche: abgedunkelte Nodes
				{
					selector: '.search-dimmed',
					style: {
						'opacity': 0.2
					}
				},
				// Suche: hervorgehobene Treffer
				{
					selector: '.search-highlight',
					style: {
						'border-color': '#e8a838',
						'border-width': 3,
						'opacity': 1
					}
				}
			];
		},

		/**
		 * Kontextmenü initialisieren
		 */
		initContextMenu: function() {
			var self = this;
			this.contextMenuEl = document.getElementById('kundenkarte-graph-contextmenu');
			if (!this.contextMenuEl) return;

			// Klick auf Menü-Eintrag
			$(this.contextMenuEl).on('click', '.ctx-item', function(e) {
				e.preventDefault();
				var action = $(this).data('action');
				var anlageId = self.contextMenuNodeId;
				if (!anlageId) return;

				var baseUrl = self.pageUrl + '?id=' + self.socId + '&system=' + self.systemId;
				switch (action) {
					case 'view':
						window.location.href = baseUrl + '&action=view&anlage_id=' + anlageId;
						break;
					case 'edit':
						window.location.href = baseUrl + '&action=edit&anlage_id=' + anlageId;
						break;
					case 'copy':
						window.location.href = baseUrl + '&action=copy&anlage_id=' + anlageId;
						break;
					case 'delete':
						window.location.href = baseUrl + '&action=delete&anlage_id=' + anlageId;
						break;
					case 'add-child':
						window.location.href = baseUrl + '&action=create&parent_id=' + anlageId;
						break;
				}
				self.hideContextMenu();
			});

			// Klick außerhalb schließt Menü
			$(document).on('click', function() {
				self.hideContextMenu();
			});
		},

		showContextMenu: function(node, renderedPos) {
			if (!this.contextMenuEl) return;
			this.contextMenuNodeId = node.data('id').replace('n_', '');

			var container = document.getElementById(this.containerId);
			var rect = container.getBoundingClientRect();
			var x = rect.left + renderedPos.x;
			var y = rect.top + renderedPos.y + window.scrollY;

			// Nicht über Bildschirmrand hinaus
			this.contextMenuEl.style.display = 'block';
			var menuW = this.contextMenuEl.offsetWidth;
			var menuH = this.contextMenuEl.offsetHeight;
			if (x + menuW > window.innerWidth) x = window.innerWidth - menuW - 10;
			if (y + menuH > window.scrollY + window.innerHeight) y = y - menuH;

			this.contextMenuEl.style.left = x + 'px';
			this.contextMenuEl.style.top = y + 'px';
		},

		hideContextMenu: function() {
			if (this.contextMenuEl) {
				this.contextMenuEl.style.display = 'none';
				this.contextMenuNodeId = null;
			}
		},

		/**
		 * Suchfunktion binden
		 */
		bindSearch: function() {
			var self = this;
			$(document).on('input', '#kundenkarte-graph-search', function() {
				var query = $(this).val().toLowerCase().trim();
				if (!self.cy) return;

				if (query === '') {
					// Alle Nodes wieder normal anzeigen
					self.cy.nodes().removeClass('search-dimmed search-highlight');
					return;
				}

				self.cy.nodes().forEach(function(node) {
					var label = (node.data('label') || '').toLowerCase();
					var typeLabel = (node.data('type_label') || '').toLowerCase();
					if (label.indexOf(query) !== -1 || typeLabel.indexOf(query) !== -1) {
						node.removeClass('search-dimmed').addClass('search-highlight');
					} else {
						node.removeClass('search-highlight').addClass('search-dimmed');
					}
				});
			});
		},

		/**
		 * Graph-Events binden
		 */
		bindGraphEvents: function() {
			var self = this;

			// Tap auf Geräte-Node -> Detail-Ansicht
			this.cy.on('tap', 'node.device-node', function(evt) {
				var node = evt.target;
				var anlageId = node.data('id').replace('n_', '');
				var url;
				if (self.isContact && self.contactId > 0) {
					url = self.moduleUrl + '/tabs/contact_anlagen.php?id=' + self.contactId
						+ '&action=view&anlage_id=' + anlageId;
				} else {
					url = self.moduleUrl + '/tabs/anlagen.php?id=' + self.socId
						+ '&action=view&anlage_id=' + anlageId;
				}
				window.location.href = url;
			});

			// Tap auf Kabel-Edge -> Verbindungs-Editor
			this.cy.on('tap', 'edge.cable-edge', function(evt) {
				var edge = evt.target;
				var connId = edge.data('connection_id');
				if (connId) {
					window.location.href = self.moduleUrl + '/anlage_connection.php?id=' + connId + '&action=edit';
				}
			});

			// Mouseover Tooltip
			this.cy.on('mouseover', 'node', function(evt) {
				self.showNodeTooltip(evt.target, evt.renderedPosition);
			});
			this.cy.on('mouseout', 'node', function() {
				self.hideTooltip();
			});
			this.cy.on('mouseover', 'edge.cable-edge', function(evt) {
				self.showEdgeTooltip(evt.target, evt.renderedPosition);
			});
			this.cy.on('mouseout', 'edge', function() {
				self.hideTooltip();
			});

			// Rechtsklick → Kontextmenü
			this.cy.on('cxttap', 'node', function(evt) {
				evt.originalEvent.preventDefault();
				self.hideTooltip();
				self.showContextMenu(evt.target, evt.renderedPosition);
			});

			// Klick auf Canvas schließt Kontextmenü
			this.cy.on('tap', function(evt) {
				if (evt.target === self.cy) {
					self.hideContextMenu();
				}
			});

			// Drag: Begrenzung auf sichtbaren Bereich
			this.cy.on('drag', 'node', function(evt) {
				var node = evt.target;
				var pos = node.position();
				var ext = self.cy.extent();
				// Etwas Rand lassen damit Node nicht am Rand klebt
				var margin = 20;
				var clampedX = Math.max(ext.x1 + margin, Math.min(ext.x2 - margin, pos.x));
				var clampedY = Math.max(ext.y1 + margin, Math.min(ext.y2 - margin, pos.y));
				if (clampedX !== pos.x || clampedY !== pos.y) {
					node.position({ x: clampedX, y: clampedY });
				}
			});

			// Drag: Snap-to-Grid + Position merken (nur im Bearbeitungsmodus)
			this.cy.on('dragfree', 'node', function(evt) {
				if (!self.editMode) return;
				var node = evt.target;
				var pos = node.position();
				var grid = self.gridSize;

				// Auf Raster einrasten
				var snappedX = Math.round(pos.x / grid) * grid;
				var snappedY = Math.round(pos.y / grid) * grid;
				node.position({ x: snappedX, y: snappedY });

				// Position zum Speichern vormerken
				var anlageId = node.data('id').replace('n_', '');
				self._dirtyNodes[anlageId] = { id: parseInt(anlageId), x: snappedX, y: snappedY };

				// Bei Compound-Nodes auch alle Kinder speichern
				var children = node.children();
				if (children.length > 0) {
					children.forEach(function(child) {
						var childPos = child.position();
						var childId = child.data('id').replace('n_', '');
						self._dirtyNodes[childId] = { id: parseInt(childId), x: childPos.x, y: childPos.y };
						// Rekursiv für verschachtelte Compounds
						child.children().forEach(function(grandchild) {
							var gcPos = grandchild.position();
							var gcId = grandchild.data('id').replace('n_', '');
							self._dirtyNodes[gcId] = { id: parseInt(gcId), x: gcPos.x, y: gcPos.y };
						});
					});
				}

			});

			// Viewport-Änderungen speichern (Pan + Zoom)
			this.cy.on('viewport', function() {
				if (self._viewportTimer) clearTimeout(self._viewportTimer);
				self._viewportTimer = setTimeout(function() {
					self.saveViewport();
				}, 300);
			});

			// Cursor
			this.cy.on('mouseover', 'node.device-node, edge.cable-edge', function() {
				document.getElementById(self.containerId).style.cursor = 'pointer';
			});
			this.cy.on('mouseout', 'node, edge', function() {
				document.getElementById(self.containerId).style.cursor = 'default';
			});
		},

		/**
		 * Zoom-Buttons binden
		 */
		bindZoomButtons: function() {
			var self = this;

			// Mausrad-Zoom Ein/Aus
			$(document).on('click', '#btn-graph-wheel-zoom', function(e) {
				e.preventDefault();
				self.wheelZoomEnabled = !self.wheelZoomEnabled;
				if (self.cy) {
					self.cy.userZoomingEnabled(self.wheelZoomEnabled);
				}
				$(this).toggleClass('active', self.wheelZoomEnabled);
				$(this).attr('title', self.wheelZoomEnabled ? 'Mausrad-Zoom aus' : 'Mausrad-Zoom ein');
			});

			$(document).on('click', '#btn-graph-fit', function(e) {
				e.preventDefault();
				if (self.cy) {
					self.cy.zoom(self.maxFitZoom);
					self.cy.center();
				}
			});
			$(document).on('click', '#btn-graph-zoom-in', function(e) {
				e.preventDefault();
				if (self.cy) {
					self.cy.zoom({
						level: self.cy.zoom() * 1.3,
						renderedPosition: { x: self.cy.width() / 2, y: self.cy.height() / 2 }
					});
				}
			});
			$(document).on('click', '#btn-graph-zoom-out', function(e) {
				e.preventDefault();
				if (self.cy) {
					self.cy.zoom({
						level: self.cy.zoom() / 1.3,
						renderedPosition: { x: self.cy.width() / 2, y: self.cy.height() / 2 }
					});
				}
			});

			// Layout zurücksetzen
			$(document).on('click', '#btn-graph-reset-layout', function(e) {
				e.preventDefault();
				self.resetLayout();
			});

			// Bearbeitungsmodus: Anordnen ein/aus
			$(document).on('click', '#btn-graph-edit-mode', function(e) {
				e.preventDefault();
				self.enterEditMode();
			});

			// Bearbeitungsmodus: Speichern
			$(document).on('click', '#btn-graph-save-positions', function(e) {
				e.preventDefault();
				self.savePositions();
				self.exitEditMode();
			});

			// Bearbeitungsmodus: Abbrechen
			$(document).on('click', '#btn-graph-cancel-edit', function(e) {
				e.preventDefault();
				self.cancelEditMode();
			});

			// PNG-Export
			$(document).on('click', '#btn-graph-export-png', function(e) {
				e.preventDefault();
				if (!self.cy) return;
				var png64 = self.cy.png({
					output: 'base64uri',
					bg: getComputedStyle(document.documentElement).getPropertyValue('--colorbackbody').trim() || '#1d1e20',
					full: true,
					scale: 2
				});
				var link = document.createElement('a');
				link.download = 'graph_export.png';
				link.href = png64;
				link.click();
			});
		},

		/**
		 * Bearbeitungsmodus aktivieren - Nodes werden verschiebbar
		 */
		enterEditMode: function() {
			if (!this.cy || this.editMode) return;
			this.editMode = true;
			this._dirtyNodes = {};

			// Aktuelle Positionen sichern (für Abbrechen)
			this._savedPositions = {};
			var self = this;
			this.cy.nodes().forEach(function(node) {
				var pos = node.position();
				self._savedPositions[node.data('id')] = { x: pos.x, y: pos.y };
			});

			// Nodes verschiebbar machen
			this.cy.autoungrabify(false);

			// Buttons umschalten
			$('#btn-graph-edit-mode').hide();
			$('#btn-graph-save-positions, #btn-graph-cancel-edit').show();

			// Visuelles Feedback: Container-Rahmen
			$('#' + this.containerId).addClass('graph-edit-mode');
		},

		/**
		 * Bearbeitungsmodus beenden (nach Speichern)
		 */
		exitEditMode: function() {
			if (!this.cy) return;
			this.editMode = false;
			this._savedPositions = {};

			// Nodes wieder sperren
			this.cy.autoungrabify(true);

			// Buttons zurückschalten
			$('#btn-graph-edit-mode').show();
			$('#btn-graph-save-positions, #btn-graph-cancel-edit').hide();

			// Visuelles Feedback entfernen
			$('#' + this.containerId).removeClass('graph-edit-mode');
		},

		/**
		 * Bearbeitungsmodus abbrechen - Positionen zurücksetzen
		 */
		cancelEditMode: function() {
			if (!this.cy) return;

			// Positionen zurücksetzen
			var self = this;
			this.cy.nodes().forEach(function(node) {
				var saved = self._savedPositions[node.data('id')];
				if (saved) {
					node.position({ x: saved.x, y: saved.y });
				}
			});

			this._dirtyNodes = {};
			this.exitEditMode();
		},

		/**
		 * Geänderte Positionen an Server senden
		 */
		savePositions: function() {
			var positions = [];
			for (var id in this._dirtyNodes) {
				positions.push(this._dirtyNodes[id]);
			}
			if (positions.length === 0) return;

			// Dirty-Liste leeren
			this._dirtyNodes = {};

			$.ajax({
				url: this.saveUrl + '?action=save',
				type: 'POST',
				contentType: 'application/json',
				data: JSON.stringify({ positions: positions }),
				dataType: 'json',
				success: function(resp) {
					if (!resp.success) {
						console.error('Graph-Positionen speichern fehlgeschlagen:', resp.error);
					}
				},
				error: function(xhr, status, err) {
					console.error('Graph-Positionen Netzwerkfehler:', status, err);
				}
			});
		},

		/**
		 * Layout zurücksetzen: Positionen löschen + dagre neu berechnen
		 */
		resetLayout: function() {
			if (!this.cy) return;
			var self = this;

			// Positionen in DB + Viewport in localStorage löschen
			$.ajax({
				url: this.saveUrl + '?action=reset&socid=' + this.socId +
					(this.contactId > 0 ? '&contactid=' + this.contactId : ''),
				type: 'POST',
				dataType: 'json'
			});
			try { localStorage.removeItem(this._viewportKey()); } catch(e) {}

			// dagre Layout neu berechnen
			this.hasPositions = false;
			this._dirtyNodes = {};
			var cy = this.cy;
			var maxZoom = this.maxFitZoom;
			cy.layout({
				name: 'dagre',
				rankDir: 'TB',
				nodeSep: 50,
				rankSep: 80,
				fit: false,
				animate: true,
				animationDuration: 400,
				stop: function() {
					// Container an neues Layout anpassen
					var bb = cy.elements().boundingBox();
					if (bb && bb.h > 0) {
						var neededH = (bb.h * maxZoom) + 100;
						var container = document.getElementById(self.containerId);
						var minH = 300;
						var maxH = window.innerHeight * 0.8;
						var newH = Math.max(minH, Math.min(maxH, neededH));
						container.style.height = Math.round(newH) + 'px';
						cy.resize();
					}
					cy.fit(undefined, 50);
					if (cy.zoom() > maxZoom) {
						cy.zoom(maxZoom);
						cy.center();
					}
				}
			}).run();
		},

		/**
		 * Node-Tooltip
		 */
		showNodeTooltip: function(node, position) {
			var data = node.data();

			// Header: Typ-Farbe als Akzentlinie
			var accentColor = data.type_color || '#5a9a6a';
			var html = '<div class="tooltip-header" style="border-left:3px solid ' + accentColor + ';padding-left:10px;">';
			// Überschrift: Icon + Bezeichnung
			html += '<div class="tooltip-title">';
			if (data.type_picto) {
				html += '<i class="fa ' + this.escapeHtml(data.type_picto) + '"></i> ';
			}
			html += this.escapeHtml(data.label) + '</div>';
			html += '</div>';

			// Hover-Felder anzeigen (nach Position sortiert, vom Backend gefiltert)
			var hoverFields = data.hover_fields || [];
			if (hoverFields.length > 0) {
				html += '<div class="tooltip-fields">';
				for (var i = 0; i < hoverFields.length; i++) {
					var f = hoverFields[i];
					if (!f.value || f.value === '') continue;
					var val = f.value;
					if (f.type === 'checkbox' && (val === '1' || val === 'true')) val = '\u2713';
					if (f.type === 'checkbox' && (val === '0' || val === 'false')) val = '\u2717';
					html += '<div class="tooltip-field">';
					html += '<span class="tooltip-field-label">' + this.escapeHtml(f.label) + '</span>';
					if (f.color) {
						html += '<span class="tooltip-field-badge" style="background:' + f.color + ';">' + this.escapeHtml(String(val)) + '</span>';
					} else {
						html += '<span class="tooltip-field-value">' + this.escapeHtml(String(val)) + '</span>';
					}
					html += '</div>';
				}
				html += '</div>';
			}

			// Datei-Info am unteren Rand
			if (data.image_count > 0 || data.doc_count > 0) {
				html += '<div class="tooltip-footer">';
				if (data.image_count > 0) html += '<span class="tooltip-file-badge"><i class="fa fa-image"></i> ' + data.image_count + '</span>';
				if (data.doc_count > 0) html += '<span class="tooltip-file-badge"><i class="fa fa-file-text-o"></i> ' + data.doc_count + '</span>';
				html += '</div>';
			}

			this.tooltipEl.innerHTML = html;
			this.tooltipEl.style.display = 'block';
			this.positionTooltip(position);
		},

		/**
		 * Edge-Tooltip
		 */
		showEdgeTooltip: function(edge, position) {
			var data = edge.data();
			// Farbe der Verbindung als Akzent
			var edgeColor = data.medium_color || '#5a8a5a';
			var html = '<div class="tooltip-header" style="border-left:3px solid ' + edgeColor + ';padding-left:10px;">';
			html += '<div class="tooltip-title"><i class="fa fa-plug"></i> Verbindung</div>';
			html += '</div>';
			html += '<div class="tooltip-fields">';
			if (data.medium_type) {
				html += '<div class="tooltip-field"><span class="tooltip-field-label">Typ</span><span class="tooltip-field-value">' + this.escapeHtml(data.medium_type) + '</span></div>';
			}
			if (data.medium_spec) {
				html += '<div class="tooltip-field"><span class="tooltip-field-label">Spezifikation</span><span class="tooltip-field-value">' + this.escapeHtml(data.medium_spec) + '</span></div>';
			}
			if (data.medium_length) {
				html += '<div class="tooltip-field"><span class="tooltip-field-label">Länge</span><span class="tooltip-field-value">' + this.escapeHtml(data.medium_length) + '</span></div>';
			}
			html += '</div>';
			this.tooltipEl.innerHTML = html;
			this.tooltipEl.style.display = 'block';
			this.positionTooltip(position);
		},

		positionTooltip: function(renderedPos) {
			var container = document.getElementById(this.containerId);
			var rect = container.getBoundingClientRect();
			var x = rect.left + renderedPos.x + 15;
			var y = rect.top + renderedPos.y - 10 + window.scrollY;
			if (x + 300 > window.innerWidth) {
				x = rect.left + renderedPos.x - 315;
			}
			this.tooltipEl.style.left = x + 'px';
			this.tooltipEl.style.top = y + 'px';
		},

		hideTooltip: function() {
			if (this.tooltipEl) this.tooltipEl.style.display = 'none';
		},

		escapeHtml: function(str) {
			if (!str) return '';
			var div = document.createElement('div');
			div.appendChild(document.createTextNode(str));
			return div.innerHTML;
		},

		destroy: function() {
			if (this.cy) { this.cy.destroy(); this.cy = null; }
			if (this.tooltipEl && this.tooltipEl.parentNode) {
				this.tooltipEl.parentNode.removeChild(this.tooltipEl);
				this.tooltipEl = null;
			}
		}
	};

	// Auto-Init
	$(document).ready(function() {
		var container = document.getElementById('kundenkarte-graph-container');
		if (container) {
			KundenKarte.Graph.ajaxUrl = container.getAttribute('data-ajax-url') || '';
			KundenKarte.Graph.saveUrl = container.getAttribute('data-save-url') || '';
			KundenKarte.Graph.moduleUrl = container.getAttribute('data-module-url') || '';
			KundenKarte.Graph.pageUrl = container.getAttribute('data-page-url') || '';
			KundenKarte.Graph.systemId = parseInt(container.getAttribute('data-systemid')) || 0;
			KundenKarte.Graph.canEdit = container.getAttribute('data-can-edit') === '1';
			KundenKarte.Graph.canDelete = container.getAttribute('data-can-delete') === '1';
			KundenKarte.Graph.init({
				socId: parseInt(container.getAttribute('data-socid')) || 0,
				contactId: parseInt(container.getAttribute('data-contactid')) || 0,
				isContact: container.hasAttribute('data-contactid') && parseInt(container.getAttribute('data-contactid')) > 0
			});
		}
	});

})();
