/**
 * MindMap D3.js Modul
 * Visualisiert die Mindmap mit D3.js
 */

class MindMapVisualization {
  constructor(containerSelector, options = {}) {
    this.containerSelector = containerSelector;
    this.container = d3.select(containerSelector);
    this.width = options.width || this.container.node().clientWidth || 800;
    this.height = options.height || 600;
    this.nodeRadius = options.nodeRadius || 22;
    this.selectedNodeRadius = options.selectedNodeRadius || 28;
    this.linkDistance = options.linkDistance || 150;
    this.chargeStrength = options.chargeStrength || -1000;
    this.centerForce = options.centerForce || 0.15;
    this.onNodeClick = options.onNodeClick || ((node) => console.log('Node clicked:', node));

    this.nodes = [];
    this.links = [];
    this.simulation = null;
    this.svg = null;
    this.linkElements = null;
    this.nodeElements = null;
    this.textElements = null;
    this.tooltipEnabled = options.tooltipEnabled !== undefined ? options.tooltipEnabled : true;
    
    this.mouseoverNode = null;
    this.selectedNode = null;
    
    this.zoomFactor = 1;
    this.tooltipDiv = null;
    this.isLoading = true;
    
    // Flash-Nachrichten-Container
    this.flashContainer = null;
    
    // Erweiterte Farbpalette für Knotentypen
    this.colorPalette = {
      'default': '#b38fff',
      'root': '#7e3ff2',
      'philosophy': '#58a9ff',
      'science': '#38b2ac',
      'technology': '#6366f1',
      'arts': '#ec4899',
      'ai': '#8b5cf6',
      'ethics': '#f59e0b',
      'math': '#06b6d4',
      'psychology': '#10b981',
      'biology': '#84cc16',
      'literature': '#f43f5e',
      'history': '#fb7185',
      'economics': '#fbbf24',
      'sociology': '#a78bfa',
      'design': '#f472b6',
      'languages': '#4ade80'
    };
    
    // Sicherstellen, dass der Container bereit ist
    if (this.container.node()) {
      this.init();
      this.setupDefaultNodes();
      this.setupFlashMessages();
      
      // Sofortige Datenladung
      window.setTimeout(() => {
        this.loadData();
      }, 100);
    } else {
      console.error('Mindmap-Container nicht gefunden:', containerSelector);
    }
  }
  
  // Flash-Nachrichten-System einrichten
  setupFlashMessages() {
    // Flash-Container erstellen, falls er noch nicht existiert
    if (!document.getElementById('mindmap-flash-container')) {
      this.flashContainer = document.createElement('div');
      this.flashContainer.id = 'mindmap-flash-container';
      this.flashContainer.className = 'mindmap-flash-container';
      this.flashContainer.style.position = 'fixed';
      this.flashContainer.style.top = '20px';
      this.flashContainer.style.right = '20px';
      this.flashContainer.style.zIndex = '1000';
      this.flashContainer.style.maxWidth = '350px';
      this.flashContainer.style.display = 'flex';
      this.flashContainer.style.flexDirection = 'column';
      this.flashContainer.style.gap = '10px';
      document.body.appendChild(this.flashContainer);
    } else {
      this.flashContainer = document.getElementById('mindmap-flash-container');
    }
    
    // Prüfen, ob Server-seitige Flash-Nachrichten existieren und anzeigen
    this.checkForServerFlashMessages();
  }
  
  // Prüft auf Server-seitige Flash-Nachrichten
  async checkForServerFlashMessages() {
    try {
      const response = await fetch('/api/get_flash_messages');
      if (response.ok) {
        const messages = await response.json();
        messages.forEach(message => {
          this.showFlash(message.message, message.category);
        });
      }
    } catch (err) {
      console.error('Fehler beim Abrufen der Flash-Nachrichten:', err);
    }
  }
  
  // Zeigt eine Flash-Nachricht an
  showFlash(message, type = 'info', duration = 5000) {
    if (!this.flashContainer) return;
    
    const flashElement = document.createElement('div');
    flashElement.className = `mindmap-flash flash-${type}`;
    flashElement.style.padding = '12px 18px';
    flashElement.style.borderRadius = '8px';
    flashElement.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
    flashElement.style.display = 'flex';
    flashElement.style.alignItems = 'center';
    flashElement.style.justifyContent = 'space-between';
    flashElement.style.fontSize = '14px';
    flashElement.style.fontWeight = '500';
    flashElement.style.backdropFilter = 'blur(10px)';
    flashElement.style.opacity = '0';
    flashElement.style.transform = 'translateY(-20px)';
    flashElement.style.transition = 'all 0.3s ease';
    
    // Spezifische Stile je nach Nachrichtentyp
    switch(type) {
      case 'success':
        flashElement.style.backgroundColor = 'rgba(34, 197, 94, 0.9)';
        flashElement.style.borderLeft = '5px solid #16a34a';
        flashElement.style.color = 'white';
        break;
      case 'error':
        flashElement.style.backgroundColor = 'rgba(239, 68, 68, 0.9)';
        flashElement.style.borderLeft = '5px solid #dc2626';
        flashElement.style.color = 'white';
        break;
      case 'warning':
        flashElement.style.backgroundColor = 'rgba(245, 158, 11, 0.9)';
        flashElement.style.borderLeft = '5px solid #d97706';
        flashElement.style.color = 'white';
        break;
      default: // info
        flashElement.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
        flashElement.style.borderLeft = '5px solid #2563eb';
        flashElement.style.color = 'white';
    }
    
    // Icon je nach Nachrichtentyp
    let icon = '';
    switch(type) {
      case 'success':
        icon = '<i class="fas fa-check-circle"></i>';
        break;
      case 'error':
        icon = '<i class="fas fa-exclamation-circle"></i>';
        break;
      case 'warning':
        icon = '<i class="fas fa-exclamation-triangle"></i>';
        break;
      default:
        icon = '<i class="fas fa-info-circle"></i>';
    }
    
    // Inhalt der Nachricht mit Icon
    const contentWrapper = document.createElement('div');
    contentWrapper.style.display = 'flex';
    contentWrapper.style.alignItems = 'center';
    contentWrapper.style.gap = '12px';
    
    const iconElement = document.createElement('div');
    iconElement.className = 'flash-icon';
    iconElement.innerHTML = icon;
    
    const textElement = document.createElement('div');
    textElement.className = 'flash-text';
    textElement.textContent = message;
    
    contentWrapper.appendChild(iconElement);
    contentWrapper.appendChild(textElement);
    
    // Schließen-Button
    const closeButton = document.createElement('button');
    closeButton.className = 'flash-close';
    closeButton.innerHTML = '<i class="fas fa-times"></i>';
    closeButton.style.background = 'none';
    closeButton.style.border = 'none';
    closeButton.style.color = 'currentColor';
    closeButton.style.cursor = 'pointer';
    closeButton.style.marginLeft = '15px';
    closeButton.style.padding = '3px';
    closeButton.style.fontSize = '14px';
    closeButton.style.opacity = '0.7';
    closeButton.style.transition = 'opacity 0.2s';
    
    closeButton.addEventListener('mouseover', () => {
      closeButton.style.opacity = '1';
    });
    
    closeButton.addEventListener('mouseout', () => {
      closeButton.style.opacity = '0.7';
    });
    
    closeButton.addEventListener('click', () => {
      this.removeFlash(flashElement);
    });
    
    // Zusammenfügen
    flashElement.appendChild(contentWrapper);
    flashElement.appendChild(closeButton);
    
    // Zum Container hinzufügen
    this.flashContainer.appendChild(flashElement);
    
    // Animation einblenden
    setTimeout(() => {
      flashElement.style.opacity = '1';
      flashElement.style.transform = 'translateY(0)';
    }, 10);
    
    // Automatisches Ausblenden nach der angegebenen Zeit
    if (duration > 0) {
      setTimeout(() => {
        this.removeFlash(flashElement);
      }, duration);
    }
    
    return flashElement;
  }
  
  // Entfernt eine Flash-Nachricht mit Animation
  removeFlash(flashElement) {
    if (!flashElement) return;
    
    flashElement.style.opacity = '0';
    flashElement.style.transform = 'translateY(-20px)';
    
    setTimeout(() => {
      if (flashElement.parentNode) {
        flashElement.parentNode.removeChild(flashElement);
      }
    }, 300);
  }
  
  // Standardknoten als Fallback einrichten, falls die API nicht reagiert
  setupDefaultNodes() {
    // Basis-Mindmap mit Hauptthemen
    const defaultNodes = [
      { id: "root", name: "Wissen", description: "Zentrale Wissensbasis", thought_count: 3 },
      { id: "philosophy", name: "Philosophie", description: "Philosophisches Denken", thought_count: 2 },
      { id: "science", name: "Wissenschaft", description: "Wissenschaftliche Erkenntnisse", thought_count: 5 },
      { id: "technology", name: "Technologie", description: "Technologische Entwicklungen", thought_count: 7 },
      { id: "arts", name: "Künste", description: "Künstlerische Ausdrucksformen", thought_count: 4 },
      { id: "ai", name: "Künstliche Intelligenz", description: "KI-Forschung und Anwendungen", thought_count: 6 },
      { id: "ethics", name: "Ethik", description: "Moralische Grundsätze", thought_count: 2 },
      { id: "math", name: "Mathematik", description: "Mathematische Konzepte", thought_count: 3 },
      { id: "psychology", name: "Psychologie", description: "Menschliches Verhalten und Kognition", thought_count: 4 },
      { id: "biology", name: "Biologie", description: "Lebenswissenschaften", thought_count: 3 },
      { id: "literature", name: "Literatur", description: "Literarische Werke und Analysen", thought_count: 2 }
    ];
    
    const defaultLinks = [
      { source: "root", target: "philosophy" },
      { source: "root", target: "science" },
      { source: "root", target: "technology" },
      { source: "root", target: "arts" },
      { source: "science", target: "math" },
      { source: "science", target: "biology" },
      { source: "technology", target: "ai" },
      { source: "philosophy", target: "ethics" },
      { source: "philosophy", target: "psychology" },
      { source: "arts", target: "literature" },
      { source: "ai", target: "ethics" },
      { source: "psychology", target: "biology" }
    ];
    
    // Als Fallback verwenden, falls die API fehlschlägt
    this.defaultNodes = defaultNodes;
    this.defaultLinks = defaultLinks;
  }
  
  init() {
    // SVG erstellen, wenn noch nicht vorhanden
    if (!this.svg) {
      // Container zuerst leeren und Loading-State ausblenden
      const loadingOverlay = this.container.select('.mindmap-loading');
      if (loadingOverlay) {
        loadingOverlay.style('opacity', 0.8);
      }
      
      this.svg = this.container
        .append('svg')
        .attr('width', '100%')
        .attr('height', this.height)
        .attr('viewBox', `0 0 ${this.width} ${this.height}`)
        .attr('class', 'mindmap-svg')
        .call(
          d3.zoom()
            .scaleExtent([0.1, 5])
            .on('zoom', (event) => {
              this.handleZoom(event.transform);
            })
        );
      
      // Hauptgruppe für alles, was zoom-transformierbar ist
      this.g = this.svg.append('g');
      
      // SVG-Definitionen für Filter und Effekte
      const defs = this.g.append('defs');
      
      // Verbesserte Glasmorphismus- und Glow-Effekte
      
      // Basis Glow-Effekt
      D3Extensions.createGlowFilter(defs, 'glow-effect', '#b38fff', 8);
      
      // Spezifische Effekte für verschiedene Zustände
      D3Extensions.createGlowFilter(defs, 'hover-glow', '#58a9ff', 6);
      D3Extensions.createGlowFilter(defs, 'selected-glow', '#b38fff', 10);
      
      // Schatten für alle Knoten
      D3Extensions.createShadowFilter(defs, 'shadow-effect');
      
      // Glasmorphismus-Effekt für Knoten
      D3Extensions.createGlassMorphismFilter(defs, 'glass-effect');
      
      // Erweiterte Effekte
      this.createAdvancedNodeEffects(defs);
      
      // Tooltip initialisieren mit verbessertem Glasmorphism-Stil
      if (!d3.select('body').select('.node-tooltip').size()) {
        this.tooltipDiv = d3.select('body')
          .append('div')
          .attr('class', 'node-tooltip')
          .style('opacity', 0)
          .style('position', 'absolute')
          .style('pointer-events', 'none')
          .style('background', 'rgba(24, 28, 45, 0.85)')
          .style('color', '#ffffff')
          .style('border', '1px solid rgba(179, 143, 255, 0.3)')
          .style('border-radius', '16px')
          .style('padding', '12px 16px')
          .style('font-size', '14px')
          .style('font-weight', '500')
          .style('line-height', '1.5')
          .style('max-width', '280px')
          .style('box-shadow', '0 12px 30px rgba(0, 0, 0, 0.5), 0 0 15px rgba(179, 143, 255, 0.25)')
          .style('backdrop-filter', 'blur(20px)')
          .style('-webkit-backdrop-filter', 'blur(20px)')
          .style('z-index', '1000');
      } else {
        this.tooltipDiv = d3.select('body').select('.node-tooltip');
      }
      
      // Initialisierung abgeschlossen - Loading-Overlay ausblenden
      setTimeout(() => {
        if (loadingOverlay) {
          loadingOverlay.transition()
            .duration(500)
            .style('opacity', 0)
            .on('end', function() {
              loadingOverlay.style('display', 'none');
            });
        }
      }, 1000);
    }
    
    // Force-Simulation initialisieren
    this.simulation = d3.forceSimulation()
      .force('link', d3.forceLink().id(d => d.id).distance(this.linkDistance))
      .force('charge', d3.forceManyBody().strength(this.chargeStrength))
      .force('center', d3.forceCenter(this.width / 2, this.height / 2).strength(this.centerForce))
      .force('collision', d3.forceCollide().radius(this.nodeRadius * 2.5));
      
    // Globale Mindmap-Instanz für externe Zugriffe setzen
    window.mindmapInstance = this;
  }
  
  // Erstellt erweiterte Effekte für die Knoten
  createAdvancedNodeEffects(defs) {
    // Verbesserte innere Leuchteffekte für Knoten
    const innerGlow = defs.append('filter')
      .attr('id', 'inner-glow')
      .attr('x', '-50%')
      .attr('y', '-50%')
      .attr('width', '200%')
      .attr('height', '200%');
      
    // Farbiger innerer Glühen
    innerGlow.append('feGaussianBlur')
      .attr('in', 'SourceAlpha')
      .attr('stdDeviation', 2)
      .attr('result', 'blur');
      
    innerGlow.append('feOffset')
      .attr('in', 'blur')
      .attr('dx', 0)
      .attr('dy', 0)
      .attr('result', 'offsetBlur');
      
    innerGlow.append('feFlood')
      .attr('flood-color', 'rgba(179, 143, 255, 0.8)')
      .attr('result', 'glowColor');
      
    innerGlow.append('feComposite')
      .attr('in', 'glowColor')
      .attr('in2', 'offsetBlur')
      .attr('operator', 'in')
      .attr('result', 'innerGlow');
      
    // Verbinden der Filter
    const innerGlowMerge = innerGlow.append('feMerge');
    innerGlowMerge.append('feMergeNode')
      .attr('in', 'innerGlow');
    innerGlowMerge.append('feMergeNode')
      .attr('in', 'SourceGraphic');
    
    // 3D-Glaseffekt mit verbesserter Tiefe
    D3Extensions.create3DGlassEffect(defs, '3d-glass');
    
    // Pulseffekt für Hervorhebung
    const pulseFilter = defs.append('filter')
      .attr('id', 'pulse-effect')
      .attr('x', '-50%')
      .attr('y', '-50%')
      .attr('width', '200%')
      .attr('height', '200%');
      
    // Animation definieren
    const pulseAnimation = pulseFilter.append('feComponentTransfer')
      .append('feFuncA')
      .attr('type', 'linear')
      .attr('slope', '1.5');
  }
  
  // Behandelt die Zoom-Transformation für die SVG
  handleZoom(transform) {
    this.g.attr('transform', transform);
    this.zoomFactor = transform.k;
    
    // Knotengröße an Zoom anpassen
    if (this.nodeElements) {
      this.nodeElements.selectAll('circle')
        .attr('r', d => {
          return d === this.selectedNode 
            ? this.selectedNodeRadius / Math.sqrt(transform.k) 
            : this.nodeRadius / Math.sqrt(transform.k);
        });
      
      this.textElements
        .style('font-size', `${16 / Math.sqrt(transform.k)}px`);
    }
  }
  
  // Lädt die Mindmap-Daten
  async loadData() {
    try {
      // Zeige Lade-Animation
      this.showLoading();
      
      // API-Aufruf durchführen, um die Kategorien und ihre Knoten zu laden
      const response = await fetch('/api/mindmap/public');
      if (!response.ok) {
        throw new Error('API-Fehler: ' + response.statusText);
      }
      
      const data = await response.json();
      console.log('Geladene Mindmap-Daten:', data);
      
      // Verarbeite die hierarchischen Daten in flache Knoten und Links
      const processed = this.processApiData(data);
      this.nodes = processed.nodes;
      this.links = processed.links;
      
      // Verbindungszählungen aktualisieren
      this.updateConnectionCounts();
      
      // Visualisierung aktualisieren
      this.updateVisualization();
      
      // Lade-Animation ausblenden
      this.hideLoading();
      
      // Erfolgreiche Ladung melden
      this.showFlash('Mindmap-Daten erfolgreich geladen', 'success');
    } catch (error) {
      console.error('Fehler beim Laden der Mindmap-Daten:', error);
      
      // Bei einem Fehler die Fallback-Daten verwenden
      this.nodes = this.defaultNodes;
      this.links = this.defaultLinks;
      
      // Verbindungszählungen auch für Fallback-Daten aktualisieren
      this.updateConnectionCounts();
      
      // Fehler anzeigen
      this.showError('Mindmap-Daten konnten nicht geladen werden. Verwende Standarddaten.');
      this.showFlash('Fehler beim Laden der Mindmap-Daten. Standarddaten werden angezeigt.', 'error');
      
      // Visualisierung auch im Fehlerfall aktualisieren
      this.updateVisualization();
      this.hideLoading();
    }
  }
  
  // Verarbeitet die API-Daten in das benötigte Format
  processApiData(apiData) {
    // Erstelle einen Root-Knoten, der alle Kategorien verbindet
    const rootNode = {
      id: "root",
      name: "Wissen",
      description: "Zentrale Wissensbasis",
      thought_count: 0
    };
    
    let nodes = [rootNode];
    let links = [];
    
    // Für jede Kategorie Knoten und Verbindungen erstellen
    apiData.forEach(category => {
      // Kategorie als Knoten hinzufügen
      const categoryNode = {
        id: `category_${category.id}`,
        name: category.name,
        description: category.description,
        color_code: category.color_code,
        icon: category.icon,
        thought_count: 0,
        type: 'category'
      };
      
      nodes.push(categoryNode);
      
      // Mit Root-Knoten verbinden
      links.push({
        source: "root",
        target: categoryNode.id
      });
      
      // Alle Knoten aus dieser Kategorie hinzufügen
      if (category.nodes && category.nodes.length > 0) {
        category.nodes.forEach(node => {
          // Zähle die Gedanken für die Kategorie
          categoryNode.thought_count += node.thought_count || 0;
          
          const mindmapNode = {
            id: `node_${node.id}`,
            name: node.name,
            description: node.description || '',
            color_code: node.color_code || category.color_code,
            thought_count: node.thought_count || 0,
            type: 'node',
            categoryId: category.id
          };
          
          nodes.push(mindmapNode);
          
          // Mit Kategorie-Knoten verbinden
          links.push({
            source: categoryNode.id,
            target: mindmapNode.id
          });
        });
      }
      
      // Rekursiv Unterkategorien verarbeiten
      if (category.children && category.children.length > 0) {
        this.processSubcategories(category.children, nodes, links, categoryNode.id);
      }
    });
    
    // Root-Knoten-Gedankenzähler aktualisieren
    rootNode.thought_count = nodes.reduce((sum, node) => sum + (node.thought_count || 0), 0);
    
    return { nodes, links };
  }
  
  // Verarbeitet Unterkategorien rekursiv
  processSubcategories(subcategories, nodes, links, parentId) {
    subcategories.forEach(category => {
      // Kategorie als Knoten hinzufügen
      const categoryNode = {
        id: `category_${category.id}`,
        name: category.name,
        description: category.description,
        color_code: category.color_code,
        icon: category.icon,
        thought_count: 0,
        type: 'subcategory'
      };
      
      nodes.push(categoryNode);
      
      // Mit Eltern-Kategorie verbinden
      links.push({
        source: parentId,
        target: categoryNode.id
      });
      
      // Alle Knoten aus dieser Kategorie hinzufügen
      if (category.nodes && category.nodes.length > 0) {
        category.nodes.forEach(node => {
          // Zähle die Gedanken für die Kategorie
          categoryNode.thought_count += node.thought_count || 0;
          
          const mindmapNode = {
            id: `node_${node.id}`,
            name: node.name,
            description: node.description || '',
            color_code: node.color_code || category.color_code,
            thought_count: node.thought_count || 0,
            type: 'node',
            categoryId: category.id
          };
          
          nodes.push(mindmapNode);
          
          // Mit Kategorie-Knoten verbinden
          links.push({
            source: categoryNode.id,
            target: mindmapNode.id
          });
        });
      }
      
      // Rekursiv Unterkategorien verarbeiten
      if (category.children && category.children.length > 0) {
        this.processSubcategories(category.children, nodes, links, categoryNode.id);
      }
    });
  }
  
  // Zeigt den Ladebildschirm an
  showLoading() {
    const loadingOverlay = this.container.select('.mindmap-loading');
    
    if (loadingOverlay && !loadingOverlay.empty()) {
      loadingOverlay
        .style('display', 'flex')
        .style('opacity', 1);
        
      // Ladebalken-Animation
      const progressBar = loadingOverlay.select('.loading-progress');
      if (!progressBar.empty()) {
        let progress = 0;
        const progressInterval = setInterval(() => {
          progress += Math.random() * 15;
          if (progress > 90) {
            progress = 90 + Math.random() * 5;
            clearInterval(progressInterval);
          }
          updateProgress(progress);
        }, 200);
        
        function updateProgress(progress) {
          progressBar.style('width', `${Math.min(progress, 95)}%`);
        }
      }
    }
  }
  
  // Blendet den Ladebildschirm aus
  hideLoading() {
    const loadingOverlay = this.container.select('.mindmap-loading');
    
    if (loadingOverlay && !loadingOverlay.empty()) {
      // Lade-Fortschritt auf 100% setzen
      const progressBar = loadingOverlay.select('.loading-progress');
      if (!progressBar.empty()) {
        progressBar.transition()
          .duration(300)
          .style('width', '100%');
      }
      
      // Overlay ausblenden
      setTimeout(() => {
        loadingOverlay.transition()
          .duration(500)
          .style('opacity', 0)
          .on('end', function() {
            loadingOverlay.style('display', 'none');
          });
      }, 400);
    }
  }
  
  // Verarbeitet hierarchische Daten in flache Knoten und Links
  processHierarchicalData(hierarchicalNodes, parentId = null) {
    let nodes = [];
    let links = [];
    
    for (const node of hierarchicalNodes) {
      nodes.push({
        id: node.id,
        name: node.name,
        description: node.description || '',
        thought_count: node.thought_count || 0
      });
      
      if (parentId) {
        links.push({
          source: parentId,
          target: node.id
        });
      }
      
      if (node.children && node.children.length > 0) {
        const { nodes: childNodes, links: childLinks } = this.processHierarchicalData(node.children, node.id);
        nodes = [...nodes, ...childNodes];
        links = [...links, ...childLinks];
      }
    }
    
    return { nodes, links };
  }
  
  // Generiert eine konsistente Farbe basierend auf dem Knotennamen
  generateColorFromString(str) {
    const colors = [
      '#b38fff', '#58a9ff', '#14b8a6', '#f472b6', '#84cc16', 
      '#f97316', '#4c1d95', '#2dd4bf', '#ec4899', '#eab308'
    ];
    
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = str.charCodeAt(i) + ((hash << 5) - hash);
    }
    
    return colors[Math.abs(hash) % colors.length];
  }
  
  /**
   * Aktualisiert die Visualisierung basierend auf den aktuellen Daten
   */
  updateVisualization() {
    if (!this.g || !this.nodes.length) return;
    
    // Daten für Simulation vorbereiten
    // Kopieren der Knoten und Links, um Referenzen zu erhalten
    const nodes = this.nodes.map(d => Object.assign({}, d));
    const links = this.links.map(d => Object.assign({}, d));
    
    // Links erstellen oder aktualisieren
    this.linkElements = this.g.selectAll('.link')
      .data(links, d => `${d.source.id || d.source}-${d.target.id || d.target}`);
    
    // Links entfernen, die nicht mehr existieren
    this.linkElements.exit().remove();
    
    // Neue Links erstellen
    const linkEnter = this.linkElements
      .enter().append('path')
      .attr('class', 'link')
      .attr('stroke-width', 2)
      .attr('stroke', 'rgba(255, 255, 255, 0.3)')
      .attr('fill', 'none')
      .attr('marker-end', 'url(#arrowhead)');
    
    // Alle Links aktualisieren
    this.linkElements = linkEnter.merge(this.linkElements);
    
    // Pfeilspitzen für die Links definieren
    if (!this.g.select('#arrowhead').size()) {
      this.g.append('defs').append('marker')
        .attr('id', 'arrowhead')
        .attr('viewBox', '0 -5 10 10')
        .attr('refX', 28) // Abstand vom Ende des Links zum Knoten
        .attr('refY', 0)
        .attr('orient', 'auto')
        .attr('markerWidth', 6)
        .attr('markerHeight', 6)
        .attr('xoverflow', 'visible')
        .append('path')
        .attr('d', 'M 0,-3 L 8,0 L 0,3')
        .attr('fill', 'rgba(255, 255, 255, 0.6)');
    }
    
    // Knoten erstellen oder aktualisieren
    this.nodeElements = this.g.selectAll('.node')
      .data(nodes, d => d.id);
    
    // Knoten entfernen, die nicht mehr existieren
    this.nodeElements.exit().remove();
    
    // Container für neue Knoten erstellen
    const nodeEnter = this.nodeElements
      .enter().append('g')
      .attr('class', 'node')
      .call(d3.drag()
        .on('start', (event, d) => this.dragStarted(event, d))
        .on('drag', (event, d) => this.dragged(event, d))
        .on('end', (event, d) => this.dragEnded(event, d))
      )
      .on('mouseover', (event, d) => this.nodeMouseover(event, d))
      .on('mouseout', (event, d) => this.nodeMouseout(event, d))
      .on('click', (event, d) => this.nodeClicked(event, d));
    
    // Kreisformen für die Knoten hinzufügen
    nodeEnter.append('circle')
      .attr('r', d => this.nodeRadius)
      .attr('fill', d => this.getNodeColor(d))
      .attr('stroke', 'rgba(255, 255, 255, 0.12)')
      .attr('stroke-width', 2)
      .style('filter', 'url(#glass-effect)');
    
    // Label für die Knoten hinzufügen
    nodeEnter.append('text')
      .attr('class', 'node-label')
      .attr('dy', 4)
      .attr('text-anchor', 'middle')
      .text(d => this.truncateNodeLabel(d.name))
      .style('font-size', d => D3Extensions.getAdaptiveFontSize(d.name, 16, 10) + 'px');
    
    // Alle Knoten aktualisieren
    this.nodeElements = nodeEnter.merge(this.nodeElements);
    
    // Simulation mit den neuen Daten aktualisieren
    this.simulation
      .nodes(nodes)
      .force('link', d3.forceLink(links).id(d => d.id).distance(this.linkDistance));
    
    // Simulation-Tick-Funktion setzen
    this.simulation.on('tick', () => this.ticked());
    
    // Simulation neustarten
    this.simulation.alpha(1).restart();
    
    // Nach kurzer Verzögerung die Knoten mit zusätzlichen Effekten versehen
    setTimeout(() => {
      this.nodeElements.selectAll('circle')
        .transition()
        .duration(500)
        .attr('r', d => {
          // Größe abhängig von der Anzahl der Gedanken
          const baseRadius = this.nodeRadius;
          const bonus = d.thought_count ? Math.min(d.thought_count / 3, 6) : 0;
          return baseRadius + bonus;
        });
    }, 300);
  }
  
  // Lange Knotenbeschriftungen abkürzen
  truncateNodeLabel(label) {
    if (!label) return '';
    
    const maxLength = 18; // Maximale Zeichenlänge
    
    if (label.length <= maxLength) {
      return label;
    } else {
      return label.substring(0, maxLength - 3) + '...';
    }
  }
  
  // Bestimmt die Farbe eines Knotens basierend auf seinem Typ oder direkt angegebener Farbe
  getNodeColor(node) {
    // Direkt angegebene Farbe verwenden, wenn vorhanden
    if (node.color_code) {
      return node.color_code;
    }
    
    // Kategorietyp-basierte Färbung
    if (node.type === 'category' || node.type === 'subcategory') {
      return this.colorPalette.root;
    }
    
    // Fallback für verschiedene Knotentypen
    return this.colorPalette[node.id] || this.colorPalette.default;
  }
  
  // Aktualisiert die Positionen in jedem Simulationsschritt
  ticked() {
    if (!this.linkElements || !this.nodeElements) return;
    
    // Aktualisierung der Linkpositionen mit gebogenem Pfad
    this.linkElements
      .attr('d', d => {
        const dx = d.target.x - d.source.x;
        const dy = d.target.y - d.source.y;
        const dr = Math.sqrt(dx * dx + dy * dy) * 1.5; // Kurvenstärke
        return `M${d.source.x},${d.source.y}A${dr},${dr} 0 0,1 ${d.target.x},${d.target.y}`;
      });
    
    // Aktualisierung der Knotenpositionen
    this.nodeElements
      .attr('transform', d => `translate(${d.x},${d.y})`);
  }
  
  // D3.js Drag-Funktionen
  dragStarted(event, d) {
    if (!event.active) this.simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  
  dragged(event, d) {
    d.fx = event.x;
    d.fy = event.y;
  }
  
  dragEnded(event, d) {
    if (!event.active) this.simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }
  
  // Hover-Effekte für Knoten
  nodeMouseover(event, d) {
    if (this.tooltipEnabled) {
      // Tooltip-Inhalt erstellen
      const tooltipContent = `
        <div class="font-medium text-lg mb-1.5" style="color: ${this.getNodeColor(d)};">${d.name}</div>
        <div class="mb-2">${d.description || 'Keine Beschreibung verfügbar'}</div>
        ${d.thought_count > 0 ? `<div><strong>${d.thought_count}</strong> Gedanken verknüpft</div>` : ''}
      `;
      
      this.tooltipDiv.html(tooltipContent)
        .style('opacity', 0.95);
      
      // Tooltip positionieren (oberhalb des Nodes)
      const nodeRect = event.target.getBoundingClientRect();
      const tooltipWidth = 250;
      const tooltipHeight = 100; // Ungefähre Höhe des Tooltips
      
      const leftPos = nodeRect.left + (nodeRect.width / 2) - (tooltipWidth / 2);
      const topPos = nodeRect.top - tooltipHeight - 10; // 10px Abstand
      
      this.tooltipDiv
        .style('left', `${leftPos}px`)
        .style('top', `${topPos}px`)
        .style('width', `${tooltipWidth}px`);
    }
    
    // Speichern des aktuellen Hover-Nodes
    this.mouseoverNode = d;
    
    // Highlights für verbundene Nodes und Links hinzufügen
    if (this.g) {
      // Verbundene Nodes identifizieren
      const connectedNodes = this.getConnectedNodesById(d.id);
      const connectedNodeIds = connectedNodes.map(node => node.id);
      
      // Alle Nodes etwas transparenter machen
      this.g.selectAll('.node')
        .transition()
        .duration(200)
        .style('opacity', node => {
          if (node.id === d.id || connectedNodeIds.includes(node.id)) {
            return 1.0;
          } else {
            return 0.5;
          }
        });
        
      // Den Hover-Node hervorheben mit größerem Radius
      this.g.selectAll('.node')
        .filter(node => node.id === d.id)
        .select('circle')
        .transition()
        .duration(200)
        .attr('r', this.nodeRadius * 1.2)
        .style('filter', 'url(#hover-glow)')
        .style('stroke', 'rgba(255, 255, 255, 0.25)');
        
      // Verbundene Links hervorheben
      this.g.selectAll('.link')
        .transition()
        .duration(200)
        .style('opacity', link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          
          if (sourceId === d.id || targetId === d.id) {
            return 0.9;
          } else {
            return 0.3;
          }
        })
        .style('stroke-width', link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          
          if (sourceId === d.id || targetId === d.id) {
            return 3;
          } else {
            return 2;
          }
        })
        .style('stroke', link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          
          if (sourceId === d.id || targetId === d.id) {
            return 'rgba(179, 143, 255, 0.7)';
          } else {
            return 'rgba(255, 255, 255, 0.3)';
          }
        });
    }
  }
  
  nodeMouseout(event, d) {
    if (this.tooltipEnabled) {
      this.tooltipDiv.transition()
        .duration(200)
        .style('opacity', 0);
    }
    
    this.mouseoverNode = null;
    
    // Highlights zurücksetzen, falls kein Node ausgewählt ist
    if (!this.selectedNode && this.g) {
      // Alle Nodes wieder auf volle Deckkraft setzen
      this.g.selectAll('.node')
        .transition()
        .duration(200)
        .style('opacity', 1.0);
      
      // Hover-Node-Radius zurücksetzen
      this.g.selectAll('.node')
        .filter(node => node.id === d.id)
        .select('circle')
        .transition()
        .duration(200)
        .attr('r', this.nodeRadius)
        .style('filter', 'none')
        .style('stroke', 'rgba(255, 255, 255, 0.12)');
      
      // Links zurücksetzen
      this.g.selectAll('.link')
        .transition()
        .duration(200)
        .style('opacity', 0.7)
        .style('stroke-width', 2)
        .style('stroke', 'rgba(255, 255, 255, 0.3)');
    }
    // Falls ein Node ausgewählt ist, den Highlight-Status für diesen beibehalten
    else if (this.selectedNode && this.g) {
      const connectedNodes = this.getConnectedNodesById(this.selectedNode.id);
      const connectedNodeIds = connectedNodes.map(node => node.id);
      
      // Alle Nodes auf den richtigen Highlight-Status setzen
      this.g.selectAll('.node')
        .transition()
        .duration(200)
        .style('opacity', node => {
          if (node.id === this.selectedNode.id || connectedNodeIds.includes(node.id)) {
            return 1.0;
          } else {
            return 0.5;
          }
        });
      
      // Hover-Node zurücksetzen, wenn er nicht der ausgewählte ist
      if (d.id !== this.selectedNode.id) {
        this.g.selectAll('.node')
          .filter(node => node.id === d.id)
          .select('circle')
          .transition()
          .duration(200)
          .attr('r', this.nodeRadius)
          .style('filter', 'none')
          .style('stroke', 'rgba(255, 255, 255, 0.12)');
      }
      
      // Links auf den richtigen Highlight-Status setzen
      this.g.selectAll('.link')
        .transition()
        .duration(200)
        .style('opacity', link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          
          if (sourceId === this.selectedNode.id || targetId === this.selectedNode.id) {
            return 0.9;
          } else {
            return 0.3;
          }
        })
        .style('stroke-width', link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          
          if (sourceId === this.selectedNode.id || targetId === this.selectedNode.id) {
            return 3;
          } else {
            return 2;
          }
        })
        .style('stroke', link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          
          if (sourceId === this.selectedNode.id || targetId === this.selectedNode.id) {
            return 'rgba(179, 143, 255, 0.7)';
          } else {
            return 'rgba(255, 255, 255, 0.3)';
          }
        });
    }
  }
  
  // Findet alle verbundenen Knoten zu einem gegebenen Knoten
  getConnectedNodes(node) {
    if (!this.links || !this.nodes || !node) return [];
    
    // Sicherstellen, dass der Knoten eine ID hat
    const nodeId = node.id || node;
    
    return this.nodes.filter(n => 
      this.links.some(link => {
        const sourceId = link.source?.id || link.source;
        const targetId = link.target?.id || link.target;
        return (sourceId === nodeId && targetId === n.id) || 
               (targetId === nodeId && sourceId === n.id);
      })
    );
  }
  
  // Prüft, ob zwei Knoten verbunden sind
  isConnected(a, b) {
    if (!this.links || !a || !b) return false;
    
    // Sicherstellen, dass die Knoten IDs haben
    const aId = a.id || a;
    const bId = b.id || b;
    
    return this.links.some(link => {
      const sourceId = link.source?.id || link.source;
      const targetId = link.target?.id || link.target;
      return (sourceId === aId && targetId === bId) || 
             (targetId === aId && sourceId === bId);
    });
  }
  
  // Überprüft, ob ein Link zwischen zwei Knoten existiert
  hasLink(source, target) {
    if (!this.links || !source || !target) return false;
    
    // Sicherstellen, dass die Knoten IDs haben
    const sourceId = source.id || source;
    const targetId = target.id || target;
    
    return this.links.some(link => {
      const linkSourceId = link.source?.id || link.source;
      const linkTargetId = link.target?.id || link.target;
      return (linkSourceId === sourceId && linkTargetId === targetId) || 
             (linkTargetId === sourceId && linkSourceId === targetId);
    });
  }
  
  // Sicherere Methode zum Abrufen verbundener Knoten, die Prüfungen enthält
  getConnectedNodesById(nodeId) {
    if (!this.links || !this.nodes || !nodeId) return [];
    
    return this.nodes.filter(n => 
      this.links.some(link => {
        const sourceId = link.source.id || link.source;
        const targetId = link.target.id || link.target;
        return (sourceId === nodeId && targetId === n.id) || 
               (targetId === nodeId && sourceId === n.id);
      })
    );
  }

  // Aktualisiert die Verbindungszählungen für alle Knoten
  updateConnectionCounts() {
    if (!this.nodes || !this.links) return;
    
    // Für jeden Knoten die Anzahl der Verbindungen berechnen
    this.nodes.forEach(node => {
      // Sichere Methode, um verbundene Knoten zu zählen
      const connectedNodes = this.nodes.filter(n => 
        n.id !== node.id && this.links.some(link => {
          const sourceId = link.source.id || link.source;
          const targetId = link.target.id || link.target;
          return (sourceId === node.id && targetId === n.id) || 
                 (targetId === node.id && sourceId === n.id);
        })
      );
      
      // Speichere die Anzahl als Eigenschaft des Knotens
      node.connectionCount = connectedNodes.length;
    });
  }
  
  // Klick-Handler für Knoten
  nodeClicked(event, d) {
    event.preventDefault();
    event.stopPropagation();
    
    // Selection-Handling: Knoten auswählen/abwählen
    if (this.selectedNode === d) {
      // Wenn der gleiche Knoten geklickt wird, Selektion aufheben
      this.selectedNode = null;
      this.nodeElements.classed('selected', false);
      
      this.nodeElements
        .select('circle:not(.node-background):not(.thought-indicator)')
        .transition()
        .duration(300)
        .attr('r', this.nodeRadius)
        .style('filter', 'url(#glass-with-reflection)')
        .attr('stroke-width', 2);
      
      // Gedankenbereich ausblenden, wenn vorhanden
      const thoughtContainer = document.getElementById('thought-container');
      if (thoughtContainer) {
        // Sanfte Ausblendanimation
        thoughtContainer.style.transition = 'all 0.3s ease-out';
        thoughtContainer.style.opacity = '0';
        thoughtContainer.style.transform = 'translateY(10px)';
        
        setTimeout(() => {
          // Gedankenbereich komplett ausblenden
          thoughtContainer.style.display = 'none';
          
          // "Empty state" anzeigen oder andere UI-Anpassungen vornehmen
          const emptyStateEl = document.getElementById('mindmap-empty-state');
          if (emptyStateEl) {
            emptyStateEl.style.display = 'flex';
            emptyStateEl.style.opacity = '0';
            setTimeout(() => {
              emptyStateEl.style.transition = 'all 0.5s ease';
              emptyStateEl.style.opacity = '1';
            }, 50);
          }
        }, 300);
      }
      
      // Alle Kanten zurücksetzen
      this.linkElements
        .classed('highlighted', false)
        .transition()
        .duration(300)
        .style('stroke', 'rgba(255, 255, 255, 0.3)')
        .style('stroke-width', 2)
        .style('opacity', 0.7);
      
      // Interface-Callback für Knoten-Abwahl
      if (typeof window.onNodeDeselected === 'function') {
        window.onNodeDeselected();
      }
      
      // Flash-Nachricht für abgewählten Knoten
      this.showFlash('Knotenauswahl aufgehoben', 'info', 2000);
      
      return;
    }
    
    // Bisher ausgewählten Knoten zurücksetzen
    if (this.selectedNode) {
      this.nodeElements
        .filter(n => n === this.selectedNode)
        .classed('selected', false)
        .select('circle:not(.node-background):not(.thought-indicator)')
        .transition()
        .duration(300)
        .attr('r', this.nodeRadius)
        .style('filter', 'url(#glass-with-reflection)')
        .attr('stroke-width', 2);
    }
    
    // Neuen Knoten auswählen
    this.selectedNode = d;
    
    // Selected-Klasse für den Knoten setzen
    this.nodeElements
      .classed('selected', n => n === d);
    
    // Visuelles Feedback für Auswahl
    this.nodeElements
      .filter(n => n === d)
      .select('circle:not(.node-background):not(.thought-indicator)')
      .transition()
      .duration(300)
      .attr('r', this.selectedNodeRadius)
      .style('filter', 'url(#selected-glow)')
      .attr('stroke-width', 3)
      .attr('stroke', 'rgba(179, 143, 255, 0.6)');
    
    // Verbundene Kanten hervorheben
    const connectedLinks = this.links.filter(link => 
      link.source === d || link.source.id === d.id || 
      link.target === d || link.target.id === d.id
    );
    
    // Alle Kanten zurücksetzen und dann verbundene hervorheben
    this.linkElements
      .classed('highlighted', false)
      .transition()
      .duration(300)
      .style('stroke', 'rgba(255, 255, 255, 0.3)')
      .style('stroke-width', 2)
      .style('opacity', 0.7);
    
    this.linkElements
      .filter(link => 
        connectedLinks.some(l => 
          (l.source === link.source || l.source.id === link.source.id) && 
          (l.target === link.target || l.target.id === link.target.id)
        )
      )
      .classed('highlighted', true)
      .transition()
      .duration(300)
      .style('stroke', 'rgba(179, 143, 255, 0.7)')
      .style('stroke-width', 3)
      .style('opacity', 0.9);
    
    // Knoten zentrieren
    this.centerNodeInView(d);
    
    // Gedanken laden
    this.loadThoughtsForNode(d);
    
    // Callback für UI-Integration
    if (typeof this.onNodeClick === 'function') {
      this.onNodeClick(d);
    }
    
    // Interface-Callback für externe Verwendung
    if (typeof window.onNodeSelected === 'function') {
      window.onNodeSelected(d);
    }
  }
  
  // Lädt die Gedanken für einen Knoten und zeigt sie an
  loadThoughtsForNode(node) {
    // UI-Element für Gedanken finden
    const thoughtContainer = document.getElementById('thought-container');
    const loadingIndicator = document.getElementById('thoughts-loading');
    const thoughtsList = document.getElementById('thoughts-list');
    const thoughtsTitle = document.getElementById('thoughts-title');
    const emptyStateEl = document.getElementById('mindmap-empty-state');
    
    if (!thoughtContainer || !thoughtsList) {
      console.error('Gedanken-Container nicht gefunden');
      this.showFlash('Fehler: Gedanken-Container nicht gefunden', 'error');
      return;
    }
    
    // "Empty state" ausblenden
    if (emptyStateEl) {
      emptyStateEl.style.transition = 'all 0.3s ease';
      emptyStateEl.style.opacity = '0';
      setTimeout(() => {
        emptyStateEl.style.display = 'none';
      }, 300);
    }
    
    // Container anzeigen mit Animation
    thoughtContainer.style.display = 'block';
    setTimeout(() => {
      thoughtContainer.style.transition = 'all 0.4s ease';
      thoughtContainer.style.opacity = '1';
      thoughtContainer.style.transform = 'translateY(0)';
    }, 50);
    
    // Titel setzen
    if (thoughtsTitle) {
      thoughtsTitle.textContent = `Gedanken zu "${node.name}"`;
    }
    
    // Ladeanimation anzeigen
    if (loadingIndicator) {
      loadingIndicator.style.display = 'flex';
    }
    
    // Bisherige Gedanken leeren
    if (thoughtsList) {
      thoughtsList.innerHTML = '';
    }
    
    // Flash-Nachricht über ausgewählten Knoten
    this.showFlash(`Knoten "${node.name}" ausgewählt`, 'info');
    
    // Verzögerung für Animation
    setTimeout(() => {
      // API-Aufruf für echte Daten aus der Datenbank
      this.fetchThoughtsForNode(node.id)
        .then(thoughts => {
          // Ladeanimation ausblenden
          if (loadingIndicator) {
            loadingIndicator.style.display = 'none';
          }
          
          // Gedanken anzeigen oder "leer"-Zustand
          if (thoughts && thoughts.length > 0) {
            this.renderThoughts(thoughts, thoughtsList);
          } else {
            this.renderEmptyThoughts(thoughtsList, node);
            this.showFlash(`Keine Gedanken zu "${node.name}" gefunden`, 'warning');
          }
        })
        .catch(error => {
          console.error('Fehler beim Laden der Gedanken:', error);
          if (loadingIndicator) {
            loadingIndicator.style.display = 'none';
          }
          this.renderErrorState(thoughtsList);
          this.showFlash('Fehler beim Laden der Gedanken. Bitte versuche es später erneut.', 'error');
        });
    }, 600); // Verzögerung für bessere UX
  }
  
  // Holt Gedanken für einen Knoten aus der Datenbank
  async fetchThoughtsForNode(nodeId) {
    try {
      // Extrahiere die tatsächliche ID aus dem nodeId Format (z.B. "node_123" oder "category_456")
      const id = nodeId.toString().split('_')[1];
      if (!id) {
        console.warn('Ungültige Node-ID: ', nodeId);
        this.showFlash('Ungültige Knoten-ID: ' + nodeId, 'warning');
        return [];
      }

      // API-Aufruf an den entsprechenden Endpunkt
      const response = await fetch(`/api/nodes/${id}/thoughts`);
      
      if (!response.ok) {
        throw new Error(`API-Fehler: ${response.statusText}`);
      }
      
      const thoughts = await response.json();
      console.log('Geladene Gedanken für Knoten:', thoughts);
      
      if (thoughts.length > 0) {
        this.showFlash(`${thoughts.length} Gedanken zum Thema geladen`, 'info');
      } else {
        this.showFlash('Keine Gedanken für diesen Knoten gefunden', 'info');
      }
      
      return thoughts;
    } catch (error) {
      console.error('Fehler beim Laden der Gedanken für Knoten:', error);
      this.showFlash('Fehler beim Laden der Gedanken', 'error');
      return [];
    }
  }
  
  // Rendert die Gedanken in der UI
  renderThoughts(thoughts, container) {
    if (!container) return;
    
    container.innerHTML = '';
    
    thoughts.forEach(thought => {
      const thoughtCard = document.createElement('div');
      thoughtCard.className = 'thought-card';
      thoughtCard.setAttribute('data-id', thought.id);
      
      const cardColor = thought.color_code || this.colorPalette.default;
      
      thoughtCard.innerHTML = `
        <div class="thought-card-header" style="border-left: 4px solid ${cardColor}">
          <h3 class="thought-title">${thought.title}</h3>
          <div class="thought-meta">
            <span class="thought-date">${new Date(thought.created_at).toLocaleDateString('de-DE')}</span>
            ${thought.author ? `<span class="thought-author">von ${thought.author.username}</span>` : ''}
          </div>
        </div>
        <div class="thought-content">
          <p>${thought.abstract || thought.content.substring(0, 150) + '...'}</p>
        </div>
        <div class="thought-footer">
          <div class="thought-keywords">
            ${thought.keywords ? thought.keywords.split(',').map(kw => 
              `<span class="keyword">${kw.trim()}</span>`).join('') : ''}
          </div>
          <a href="/thoughts/${thought.id}" class="thought-link">Mehr lesen →</a>
        </div>
      `;
      
      // Event-Listener für Klick auf Gedanken
      thoughtCard.addEventListener('click', (e) => {
        // Verhindern, dass der Link-Klick den Kartenklick auslöst
        if (e.target.tagName === 'A') return;
        window.location.href = `/thoughts/${thought.id}`;
      });
      
      container.appendChild(thoughtCard);
    });
  }
  
  // Rendert eine Leermeldung, wenn keine Gedanken vorhanden sind
  renderEmptyThoughts(container, node) {
    if (!container) return;
    
    const emptyState = document.createElement('div');
    emptyState.className = 'empty-thoughts-state';
    
    emptyState.innerHTML = `
      <div class="empty-icon">
        <i class="fas fa-lightbulb"></i>
      </div>
      <h3>Keine Gedanken verknüpft</h3>
      <p>Zu "${node.name}" sind noch keine Gedanken verknüpft.</p>
      <div class="empty-actions">
        <a href="/add-thought?node=${node.id}" class="btn btn-primary">
          <i class="fas fa-plus-circle"></i> Gedanken hinzufügen
        </a>
      </div>
    `;
    
    container.appendChild(emptyState);
  }
  
  // Rendert einen Fehlerzustand
  renderErrorState(container) {
    if (!container) return;
    
    const errorState = document.createElement('div');
    errorState.className = 'error-thoughts-state';
    
    errorState.innerHTML = `
      <div class="error-icon">
        <i class="fas fa-exclamation-triangle"></i>
      </div>
      <h3>Fehler beim Laden</h3>
      <p>Die Gedanken konnten nicht geladen werden. Bitte versuche es später erneut.</p>
      <button class="btn btn-secondary retry-button">
        <i class="fas fa-redo"></i> Erneut versuchen
      </button>
    `;
    
    // Event-Listener für Retry-Button
    const retryButton = errorState.querySelector('.retry-button');
    if (retryButton && this.selectedNode) {
      retryButton.addEventListener('click', () => {
        this.loadThoughtsForNode(this.selectedNode);
      });
    }
    
    container.appendChild(errorState);
  }
  
  // Zentriert einen Knoten in der Ansicht
  centerNodeInView(node) {
    // Sanfter Übergang zur Knotenzentrierüng
    const transform = d3.zoomTransform(this.svg.node());
    const scale = transform.k;
    
    const x = -node.x * scale + this.width / 2;
    const y = -node.y * scale + this.height / 2;
    
    this.svg.transition()
      .duration(750)
      .call(
        d3.zoom().transform,
        d3.zoomIdentity.translate(x, y).scale(scale)
      );
      
    // Flash-Nachricht für Zentrierung
    if (node && node.name) {
      this.showFlash(`Ansicht auf "${node.name}" zentriert`, 'info', 2000);
    }
  }
  
  // Fehlermeldung anzeigen
  showError(message) {
    // Standard-Fehlermeldung als Banner 
    const errorBanner = d3.select('body').selectAll('.error-banner').data([0]);
    
    const errorEnter = errorBanner.enter()
      .append('div')
      .attr('class', 'error-banner')
      .style('position', 'fixed')
      .style('bottom', '-100px')
      .style('left', '50%')
      .style('transform', 'translateX(-50%)')
      .style('background', 'rgba(220, 38, 38, 0.9)')
      .style('color', 'white')
      .style('padding', '12px 20px')
      .style('border-radius', '8px')
      .style('z-index', '1000')
      .style('box-shadow', '0 10px 25px rgba(0, 0, 0, 0.3)')
      .style('font-weight', '500')
      .style('max-width', '90%')
      .style('text-align', 'center');
    
    const banner = errorEnter.merge(errorBanner);
    banner.html(`<i class="fa-solid fa-circle-exclamation mr-2"></i> ${message}`)
      .transition()
      .duration(500)
      .style('bottom', '20px')
      .transition()
      .delay(5000)
      .duration(500)
      .style('bottom', '-100px');
      
    // Auch als Flash-Nachricht anzeigen
    this.showFlash(message, 'error');
  }

  // Fokussieren auf einen bestimmten Knoten per ID
  focusNode(nodeId) {
    const targetNode = this.nodes.find(n => n.id === nodeId);
    if (!targetNode) {
      this.showFlash(`Knoten mit ID "${nodeId}" nicht gefunden`, 'error');
      return;
    }
    
    // Ausgewählten Zustand zurücksetzen
    this.selectedNode = null;
    
    // Node-Klick simulieren
    this.nodeClicked(null, targetNode);
    
    // Fokussieren mit einer Animation
    if (targetNode.x && targetNode.y) {
      const transform = d3.zoomIdentity
        .translate(this.width / 2 - targetNode.x * 1.2, this.height / 2 - targetNode.y * 1.2)
        .scale(1.2);
      
      this.svg.transition()
        .duration(750)
        .call(
          d3.zoom().transform,
          transform
        );
      
      this.showFlash(`Fokus auf Knoten "${targetNode.name}" gesetzt`, 'success');
    }
  }
  
  // Filtert Knoten nach Suchbegriff
  filterBySearchTerm(searchTerm) {
    if (!searchTerm || searchTerm.trim() === '') {
      // Alle Knoten anzeigen, wenn kein Suchbegriff
      this.nodeElements
        .style('display', 'block')
        .selectAll('circle')
        .style('opacity', 1);
      
      this.textElements
        .style('opacity', 1);
      
      this.linkElements
        .style('display', 'block')
        .style('stroke-opacity', 0.5);
      
      this.showFlash('Suchfilter zurückgesetzt', 'info', 2000);
      
      return;
    }
    
    searchTerm = searchTerm.toLowerCase().trim();
    
    // Knoten finden, die dem Suchbegriff entsprechen
    const matchingNodes = this.nodes.filter(node => 
      node.name.toLowerCase().includes(searchTerm) || 
      (node.description && node.description.toLowerCase().includes(searchTerm))
    );
    
    const matchingNodeIds = matchingNodes.map(n => n.id);
    
    // Nur passende Knoten und ihre Verbindungen anzeigen
    this.nodeElements
      .style('display', d => matchingNodeIds.includes(d.id) ? 'block' : 'none')
      .selectAll('circle')
      .style('opacity', 1);
    
    this.textElements
      .style('opacity', d => matchingNodeIds.includes(d.id) ? 1 : 0.2);
    
    this.linkElements
      .style('display', link => 
        matchingNodeIds.includes(link.source.id) && matchingNodeIds.includes(link.target.id) ? 'block' : 'none')
      .style('stroke-opacity', 0.7);
    
    // Wenn nur ein Knoten gefunden wurde, darauf fokussieren
    if (matchingNodes.length === 1) {
      this.focusNode(matchingNodes[0].id);
    }
    
    // Wenn mehr als ein Knoten gefunden wurde, Simulation mit reduzierter Stärke neu starten
    if (matchingNodes.length > 1) {
      this.simulation.alpha(0.3).restart();
      this.showFlash(`${matchingNodes.length} Knoten für "${searchTerm}" gefunden`, 'success');
    } else if (matchingNodes.length === 0) {
      this.showFlash(`Keine Knoten für "${searchTerm}" gefunden`, 'warning');
    }
  }
}

// D3-Erweiterungen für spezielle Effekte
class D3Extensions {
  static createGlowFilter(defs, id, color = '#b38fff', strength = 5) {
    const filter = defs.append('filter')
      .attr('id', id)
      .attr('height', '300%')
      .attr('width', '300%')
      .attr('x', '-100%')
      .attr('y', '-100%');
    
    // Farbe und Sättigung
    const colorMatrix = filter.append('feColorMatrix')
      .attr('type', 'matrix')
      .attr('values', `
        1 0 0 0 ${color === '#b38fff' ? 0.7 : 0.35}
        0 1 0 0 ${color === '#58a9ff' ? 0.7 : 0.35}
        0 0 1 0 ${color === '#58a9ff' ? 0.7 : 0.55}
        0 0 0 1 0
      `)
      .attr('result', 'colored');
    
    // Weichzeichner für Glühen
    const blur = filter.append('feGaussianBlur')
      .attr('in', 'colored')
      .attr('stdDeviation', strength)
      .attr('result', 'blur');
    
    // Kombination von Original und Glühen
    const merge = filter.append('feMerge');
    
    merge.append('feMergeNode')
      .attr('in', 'blur');
    
    merge.append('feMergeNode')
      .attr('in', 'SourceGraphic');
    
    return filter;
  }
  
  static createShadowFilter(defs, id) {
    const filter = defs.append('filter')
      .attr('id', id)
      .attr('height', '200%')
      .attr('width', '200%')
      .attr('x', '-50%')
      .attr('y', '-50%');
    
    // Offset der Lichtquelle
    const offset = filter.append('feOffset')
      .attr('in', 'SourceAlpha')
      .attr('dx', 3)
      .attr('dy', 4)
      .attr('result', 'offset');
    
    // Weichzeichnung für Schatten
    const blur = filter.append('feGaussianBlur')
      .attr('in', 'offset')
      .attr('stdDeviation', 5)
      .attr('result', 'blur');
    
    // Schatten-Opazität
    const opacity = filter.append('feComponentTransfer');
    
    opacity.append('feFuncA')
      .attr('type', 'linear')
      .attr('slope', 0.3);
    
    // Zusammenführen
    const merge = filter.append('feMerge');
    
    merge.append('feMergeNode');
    merge.append('feMergeNode')
      .attr('in', 'SourceGraphic');
    
    return filter;
  }
  
  static createGlassMorphismFilter(defs, id) {
    const filter = defs.append('filter')
      .attr('id', id)
      .attr('width', '300%')
      .attr('height', '300%')
      .attr('x', '-100%')
      .attr('y', '-100%');
    
    // Basis-Hintergrundfarbe
    const bgColor = filter.append('feFlood')
      .attr('flood-color', 'rgba(24, 28, 45, 0.75)')
      .attr('result', 'bgColor');
    
    // Weichzeichnung des Originalelements
    const blur = filter.append('feGaussianBlur')
      .attr('in', 'SourceGraphic')
      .attr('stdDeviation', '3')
      .attr('result', 'blur');
    
    // Komposition des Glaseffekts mit Original
    const composite1 = filter.append('feComposite')
      .attr('in', 'bgColor')
      .attr('in2', 'blur')
      .attr('operator', 'in')
      .attr('result', 'glass');
    
    // Leichter Farbakzent
    const colorMatrix = filter.append('feColorMatrix')
      .attr('in', 'glass')
      .attr('type', 'matrix')
      .attr('values', '1 0 0 0 0.1  0 1 0 0 0.1  0 0 1 0 0.3  0 0 0 1 0')
      .attr('result', 'coloredGlass');
    
    // Leichte Transparenz an den Rändern
    const specLight = filter.append('feSpecularLighting')
      .attr('in', 'blur')
      .attr('surfaceScale', '3')
      .attr('specularConstant', '0.75')
      .attr('specularExponent', '20')
      .attr('lighting-color', '#ffffff')
      .attr('result', 'specLight');
    
    specLight.append('fePointLight')
      .attr('x', '-20')
      .attr('y', '-30')
      .attr('z', '120');
    
    // Lichtkombination
    const composite2 = filter.append('feComposite')
      .attr('in', 'specLight')
      .attr('in2', 'coloredGlass')
      .attr('operator', 'in')
      .attr('result', 'lightedGlass');
    
    // Alle Effekte kombinieren
    const merge = filter.append('feMerge')
      .attr('result', 'glassMerge');
    
    merge.append('feMergeNode')
      .attr('in', 'coloredGlass');
    
    merge.append('feMergeNode')
      .attr('in', 'lightedGlass');
    
    merge.append('feMergeNode')
      .attr('in', 'SourceGraphic');
    
    return filter;
  }
  
  // Erstellt einen erweiterten 3D-Glaseffekt mit Lichtreflexion
  static create3DGlassEffect(defs, id) {
    const filter = defs.append('filter')
      .attr('id', id)
      .attr('width', '300%')
      .attr('height', '300%')
      .attr('x', '-100%')
      .attr('y', '-100%');
    
    // Hintergrund-Färbung mit Transparenz
    const bgColor = filter.append('feFlood')
      .attr('flood-color', 'rgba(24, 28, 45, 0.7)')
      .attr('result', 'bgColor');
    
    // Alpha-Kanal modifizieren
    const composite1 = filter.append('feComposite')
      .attr('in', 'bgColor')
      .attr('in2', 'SourceAlpha')
      .attr('operator', 'in')
      .attr('result', 'shape');
    
    // Leichte Unschärfe hinzufügen
    const blur = filter.append('feGaussianBlur')
      .attr('in', 'shape')
      .attr('stdDeviation', '2')
      .attr('result', 'blurredShape');
    
    // Lichtquelle für 3D-Effekt
    const specLight = filter.append('feSpecularLighting')
      .attr('in', 'blurredShape')
      .attr('surfaceScale', '5')
      .attr('specularConstant', '1')
      .attr('specularExponent', '20')
      .attr('lighting-color', '#ffffff')
      .attr('result', 'specLight');
    
    specLight.append('fePointLight')
      .attr('x', '50')
      .attr('y', '-50')
      .attr('z', '200');
    
    // Farbmatrix für Lichttönung
    const colorMatrix = filter.append('feColorMatrix')
      .attr('in', 'specLight')
      .attr('type', 'matrix')
      .attr('values', '1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0')
      .attr('result', 'coloredLight');
    
    // Alle Effekte kombinieren
    const merge = filter.append('feMerge');
    
    merge.append('feMergeNode')
      .attr('in', 'blurredShape');
    
    merge.append('feMergeNode')
      .attr('in', 'coloredLight');
    
    merge.append('feMergeNode')
      .attr('in', 'SourceGraphic');
    
    return filter;
  }
}

// Globales Objekt für Zugriff außerhalb des Moduls
window.MindMapVisualization = MindMapVisualization;