{# templates/connections.html #}
{% extends 'base.html' %}

{% block title %}Recent Connections{% endblock %}

{% block head_extra %}
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
  <style>
    /* page-specific styles */
    .split-view {
      display: flex;
      gap: 20px;
      height: 85vh;
    }
    .map-section {
      flex: 1;
      display: flex;
      flex-direction: column;
      min-width: 0;
      min-height: 0;
    }
    .table-section {
      flex: 1;
      display: flex;
      flex-direction: column;
      min-width: 0;
    }
    #map {
      width: 100%;
      flex: 1;
      height: auto;
      min-height: 320px;
      border-radius: 8px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.2);
    }
    .section-header {
      background: white;
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 15px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    .page-content {
      padding: 20px;
    }
    .stats {
      display: flex;
      gap: 20px;
      flex-wrap: wrap;
      margin-top: 10px;
    }
    .stat-item {
      flex: 1;
      min-width: 120px;
    }
    .stat-label {
      font-size: 11px;
      color: #666;
      text-transform: uppercase;
    }
    .stat-value {
      font-size: 20px;
      font-weight: bold;
      color: #333;
    }
    .table-container { 
      flex: 1; 
      overflow-y: auto; 
      background: white;
      border-radius: 8px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    #connectionsTableBody {
      transition: transform 0.5s ease-out;
      transform: translateY(0);
    }
    @keyframes slideIn {
      0% { opacity: 0; transform: scale(0.8); }
      100% { opacity: 1; transform: scale(1); }
    }
    .slide-in { animation: slideIn 0.5s ease-out; }
    @keyframes slideOut {
      0% { opacity: 1; transform: scale(1); }
      100% { opacity: 0; transform: scale(0.1); }
    }
    .slide-out { animation: slideOut 0.5s ease-in forwards; }
    
    @media (max-width: 1200px) {
      .split-view {
        flex-direction: column;
        height: auto;
      }
      .map-section, .table-section {
        min-height: 400px;
      }
    }
  </style>
{% endblock %}

{% block content %}
  <div class="page-content">
    <div class="section-header">
      <h2 style="margin: 0;">Verbindungen der letzten 10 Minuten</h2>
      <div class="stats">
        <div class="stat-item">
          <div class="stat-label">Last Connection</div>
          <div class="stat-value" id="last-connection" style="font-size: 14px;">-</div>
        </div>
        <div class="stat-item">
          <div class="stat-label">Total Connections</div>
          <div class="stat-value" id="totalConnections">0</div>
        </div>
        <div class="stat-item">
          <div class="stat-label">Total File Size</div>
          <div class="stat-value" id="totalSize">0 B</div>
        </div>
        <div class="stat-item">
          <div class="stat-label">Cached %</div>
          <div class="stat-value" id="cachedPercent">0%</div>
        </div>
      </div>
    </div>

    <div class="split-view">
      <!-- Map Section -->
      <div class="map-section">
        <div id="map"></div>
      </div>

      <!-- Table Section -->
      <div class="table-section">
        <div class="table-container">
          <table class="table table-hover">
            <thead class="table-secondary">
              <tr>
                <th>Timestamp</th>
                <th>Location</th>
                <th>User Agent</th>
                <th>File Path</th>
                <th>File Size</th>
                <th>MIME-Typ</th>
                <th>Cached</th>
              </tr>
            </thead>
            <tbody id="connectionsTableBody">
              {# dynamically populated via Socket.IO #}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
{% endblock %}

{% block scripts %}
  <script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
  <script>
    // Initialize the map centered on Europe
    const map = L.map('map').setView([50.0, 10.0], 5);
    const refreshMapSize = () => setTimeout(() => 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);

    // Store active dots with their data
    const activeDots = new Map();
    let totalConnections = 0;

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

    // WebSocket connection
    const socket = io();
    socket.on("connect", () => {
      socket.emit("request_initial_data");
      socket.emit("request_map_data");
    });

    // Map functions
    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);
            totalConnections++;
        }
        updateMapStats();
    }

    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 updateMapStats() {
        let mostRecent = null;
        activeDots.forEach(dot => {
            if (!mostRecent || dot.lastUpdate > mostRecent) {
                mostRecent = dot.lastUpdate;
            }
        });
        
        if (mostRecent) {
            const timeAgo = getTimeAgo(mostRecent);
            document.getElementById('last-connection').textContent = timeAgo;
        }
    }

    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 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() {
        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();
    }

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

    // Handle map data
    socket.on('map_initial_data', function(data) {
        console.log('Received map data:', data.connections.length, 'connections');
        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) {
        console.log('New connection:', data);
        if (data.lat && data.lon) {
            addOrUpdateDot(data.lat, data.lon, data.city, data.country, data.timestamp);
        }
    });

    // Table functions
    function updateStats(data) {
      document.getElementById("totalConnections").textContent = data.length;
      const totalBytes = data.reduce((sum, rec) => {
        const val = parseFloat(rec.filesize);
        return sum + (isNaN(val) ? 0 : val);
      }, 0);
      document.getElementById("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;
      document.getElementById("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");
      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");
      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);
      animateTableWithNewRow(data);
    });
  </script>
{% endblock %}
