(() => {
  let map = null;
  let socket = null;
  let activeDots = null;
  let animateInterval = null;
  let statsInterval = null;
  let refreshMapSize = null;
  const TABLE_LIMIT = 20;

  function initConnectionsPage() {
    const mapEl = document.getElementById('map');
    if (!mapEl || mapEl.dataset.bound) return;
    if (typeof L === 'undefined' || typeof io === 'undefined') return;

    mapEl.dataset.bound = '1';
    activeDots = new Map();

    // Initialize the map centered on Europe
    map = L.map('map').setView([50.0, 10.0], 5);
    refreshMapSize = () => setTimeout(() => map && map.invalidateSize(), 150);
    refreshMapSize();
    window.addEventListener('resize', refreshMapSize);
    window.addEventListener('orientationchange', refreshMapSize);

    // Add OpenStreetMap tile layer
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '© OpenStreetMap contributors',
      maxZoom: 19
    }).addTo(map);

    // Custom pulsing circle marker
    const PulsingMarker = L.CircleMarker.extend({
      options: {
        radius: 10,
        fillColor: '#ff4444',
        color: '#ff0000',
        weight: 2,
        opacity: 1,
        fillOpacity: 0.8
      }
    });

    function updatePopup(dot) {
      const location = dot.connections[0];
      const content = `
        <strong>${location.city}, ${location.country}</strong><br>
        Connections: ${dot.mass}<br>
        Latest: ${new Date(location.timestamp).toLocaleString()}
      `;
      dot.marker.bindPopup(content);
    }

    function getTimeAgo(timestamp) {
      const seconds = Math.floor((Date.now() - timestamp) / 1000);
      if (seconds < 60) return `${seconds}s ago`;
      const minutes = Math.floor(seconds / 60);
      if (minutes < 60) return `${minutes}m ago`;
      const hours = Math.floor(minutes / 60);
      return `${hours}h ago`;
    }

    function updateMapStats() {
      let mostRecent = null;
      activeDots.forEach(dot => {
        if (!mostRecent || dot.lastUpdate > mostRecent) {
          mostRecent = dot.lastUpdate;
        }
      });

      if (mostRecent) {
        const timeAgo = getTimeAgo(mostRecent);
        const lastConnection = document.getElementById('last-connection');
        if (lastConnection) lastConnection.textContent = timeAgo;
      }
    }

    function addOrUpdateDot(lat, lon, city, country, timestamp) {
      const key = `${lat},${lon}`;
      const now = Date.now();

      if (activeDots.has(key)) {
        const dot = activeDots.get(key);
        dot.mass += 1;
        dot.lastUpdate = now;
        dot.connections.push({ city, country, timestamp });

        const newRadius = 10 + Math.log(dot.mass) * 5;
        dot.marker.setRadius(newRadius);
        updatePopup(dot);
      } else {
        const marker = new PulsingMarker([lat, lon], {
          radius: 10
        }).addTo(map);

        const dot = {
          marker: marker,
          mass: 1,
          createdAt: now,
          lastUpdate: now,
          lat: lat,
          lon: lon,
          connections: [{ city, country, timestamp }]
        };

        activeDots.set(key, dot);
        updatePopup(dot);
      }
      updateMapStats();
    }

    function formatBytes(bytes) {
      if (!bytes && bytes !== 0) return '-';
      const units = ['B', 'KB', 'MB', 'GB', 'TB'];
      let num = bytes;
      let unitIndex = 0;
      while (num >= 1024 && unitIndex < units.length - 1) {
        num /= 1024;
        unitIndex++;
      }
      const rounded = num % 1 === 0 ? num.toFixed(0) : num.toFixed(1);
      return `${rounded} ${units[unitIndex]}`;
    }

    function toBool(val) {
      if (val === true || val === 1) return true;
      if (typeof val === 'string') {
        const lower = val.toLowerCase();
        return lower === 'true' || lower === '1' || lower === 'yes';
      }
      return false;
    }

    function animateDots() {
      if (!map) return;
      const now = Date.now();
      const fadeTime = 10 * 60 * 1000; // 10 minutes

      activeDots.forEach((dot, key) => {
        const age = now - dot.lastUpdate;

        if (age > fadeTime) {
          map.removeLayer(dot.marker);
          activeDots.delete(key);
        } else {
          const fadeProgress = age / fadeTime;
          const baseRadius = 10 + Math.log(dot.mass) * 5;
          const currentRadius = baseRadius * (1 - fadeProgress * 0.8);
          dot.marker.setRadius(Math.max(currentRadius, 2));

          const opacity = 1 - fadeProgress * 0.7;
          dot.marker.setStyle({
            fillOpacity: opacity * 0.8,
            opacity: opacity
          });
        }
      });

      updateMapStats();
    }

    animateInterval = setInterval(animateDots, 100);
    statsInterval = setInterval(() => {
      if (activeDots.size > 0) {
        updateMapStats();
      }
    }, 1000);

    // WebSocket connection
    // Force polling to avoid websocket upgrade issues behind some proxies.
    socket = io({
      transports: ['polling'],
      upgrade: false
    });
    socket.on('connect', () => {
      socket.emit('request_initial_data');
      socket.emit('request_map_data');
    });

    socket.on('map_initial_data', function(data) {
      data.connections.forEach(conn => {
        if (conn.lat && conn.lon) {
          addOrUpdateDot(conn.lat, conn.lon, conn.city, conn.country, conn.timestamp);
        }
      });
    });

    socket.on('new_connection', function(data) {
      if (data.lat && data.lon) {
        addOrUpdateDot(data.lat, data.lon, data.city, data.country, data.timestamp);
      }
    });

    function updateStats(data) {
      const headerCount = document.getElementById('connectionsTotalHeader');
      if (headerCount) headerCount.textContent = `${data.length} Verbindungen`;
      const totalConnections = document.getElementById('totalConnections');
      if (totalConnections) totalConnections.textContent = data.length;
      const totalBytes = data.reduce((sum, rec) => {
        const val = parseFloat(rec.filesize);
        return sum + (isNaN(val) ? 0 : val);
      }, 0);
      const totalSize = document.getElementById('totalSize');
      if (totalSize) totalSize.textContent = formatBytes(totalBytes);
      const cachedCount = data.reduce((sum, rec) => sum + (toBool(rec.cached) ? 1 : 0), 0);
      const pct = data.length ? ((cachedCount / data.length) * 100).toFixed(0) : 0;
      const cachedPercent = document.getElementById('cachedPercent');
      if (cachedPercent) cachedPercent.textContent = `${pct}%`;
    }

    function createRow(record, animate = true) {
      const tr = document.createElement('tr');
      tr.dataset.timestamp = record.timestamp;
      if (animate) {
        tr.classList.add('slide-in');
        tr.addEventListener('animationend', () =>
          tr.classList.remove('slide-in'), { once: true });
      }
      tr.innerHTML = `
        <td>${record.timestamp}</td>
        <td>${record.location}</td>
        <td>${record.user_agent}</td>
        <td>${record.full_path}</td>
        <td title="${record.filesize}">${record.filesize_human || record.filesize}</td>
        <td>${record.mime_typ}</td>
        <td>${record.cached}</td>
      `;
      return tr;
    }

    function updateTable(data) {
      const tbody = document.getElementById('connectionsTableBody');
      if (!tbody) return;
      const existing = {};
      Array.from(tbody.children).forEach(r => {
        existing[r.dataset.timestamp] = r;
      });

      const frag = document.createDocumentFragment();
      data.forEach(rec => {
        let row = existing[rec.timestamp];
        if (row) {
          row.innerHTML = createRow(rec, false).innerHTML;
          row.classList.remove('slide-out');
        } else {
          row = createRow(rec, true);
        }
        frag.appendChild(row);
      });

      tbody.innerHTML = '';
      tbody.appendChild(frag);
    }

    function animateTableWithNewRow(data) {
      const tbody = document.getElementById('connectionsTableBody');
      if (!tbody) return;
      const currentTs = new Set([...tbody.children].map(r => r.dataset.timestamp));
      const newRecs = data.filter(r => !currentTs.has(r.timestamp));

      if (newRecs.length) {
        const temp = createRow(newRecs[0], false);
        temp.style.visibility = 'hidden';
        tbody.appendChild(temp);
        const h = temp.getBoundingClientRect().height;
        temp.remove();

        tbody.style.transform = `translateY(${h}px)`;
        setTimeout(() => {
          updateTable(data);
          tbody.style.transition = 'none';
          tbody.style.transform = 'translateY(0)';
          void tbody.offsetWidth;
          tbody.style.transition = 'transform 0.5s ease-out';
        }, 500);
      } else {
        updateTable(data);
      }
    }

    socket.on('recent_connections', data => {
      updateStats(data);
      const tableData = data.slice(0, TABLE_LIMIT);
      animateTableWithNewRow(tableData);
    });
  }

  function destroyConnectionsPage() {
    if (socket) {
      socket.off();
      socket.disconnect();
      socket = null;
    }
    if (animateInterval) {
      clearInterval(animateInterval);
      animateInterval = null;
    }
    if (statsInterval) {
      clearInterval(statsInterval);
      statsInterval = null;
    }
    if (refreshMapSize) {
      window.removeEventListener('resize', refreshMapSize);
      window.removeEventListener('orientationchange', refreshMapSize);
      refreshMapSize = null;
    }
    if (map) {
      map.remove();
      map = null;
    }
    activeDots = null;
  }

  if (window.PageRegistry && typeof window.PageRegistry.register === 'function') {
    window.PageRegistry.register('connections', {
      init: initConnectionsPage,
      destroy: destroyConnectionsPage
    });
  }
})();
