{% extends "base.html" %}
{% from "_form_helpers.html" import render_field %}

{% block extra_head %} {# Assuming base.html has an 'extra_head' block for additional head elements #}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/fire-animation.css') }}">
{% endblock %}

{% block content %}
{# Main page content for searching and importing packages #}

<div class="container">
    {# Flash messages area - Ensure _flash_messages.html exists and is included in base.html or rendered here #}
    {% include "_flash_messages.html" %}

    {# Display warning if package fetching failed on initial load #}
    {% if fetch_failed %}
    <div class="alert alert-warning">
        Unable to fetch packages from registries. Showing a fallback list. Please try again later or contact support.
    </div>
    {% endif %}

    <div class="row">
        {# Left Column: Search Packages Area #}
        <div class="col-md-9 mb-4">
            <div class="card">
                <div class="card-body">
                    {# Header with Refresh Button #}
                    <div class="d-flex justify-content-between align-items-center mb-3">
                        <h2><i class="bi bi-search me-2"></i>Search Packages</h2>
                        <button class="btn btn-sm btn-outline-warning" id="clear-cache-btn"
                                title="Clear package cache and fetch fresh list"
                                hx-post="{{ url_for('refresh_cache_task') }}"
                                hx-indicator="#cache-spinner"
                                hx-swap="none"
                                hx-target="this">
                            <i class="bi bi-arrow-clockwise"></i> Clear & Refresh Cache
                            <span id="cache-spinner" class="spinner-border spinner-border-sm ms-1" role="status" aria-hidden="true" style="display: none;"></span>
                        </button>
                    </div>

                    {# Cache Status Timestamp #}
                    <div class="mb-3 text-muted" style="font-size: 0.85em;">
                         {% if last_cached_timestamp %}
                            Package list last fetched: {{ last_cached_timestamp.strftime('%Y-%m-%d %H:%M:%S %Z') if last_cached_timestamp else 'Never' }}
                            {% if fetch_failed %} (Fetch Failed){% endif %}
                        {% elif is_fetching %} {# Show text spinner specifically during initial fetch state triggered by backend #}
                            Fetching package list... <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                        {% else %}
                             Never fetched or cache cleared.
                        {% endif %}
                    </div>

                    {# Search Input with HTMX #}
                    <div class="input-group mb-3">
                        <span class="input-group-text"><i class="bi bi-search"></i></span>
                        <input type="search" class="form-control" placeholder="Search packages..." name="search" autofocus
                               hx-get="{{ url_for('api_search_packages') }}" hx-indicator=".htmx-indicator" hx-target="#search-results" hx-trigger="input changed delay:500ms, search">
                        <span class="input-group-text htmx-indicator"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></span>
                    </div>
                    <div id="search-error" class="alert alert-danger d-none"></div>

                    {# Search Results Area (populated by HTMX) #}
                    <div id="search-results">
                        {% include '_search_results_table.html' %} {# Includes the initial table state or updated results #}
                    </div>
                </div>
            </div>
        </div>

        {# Right Column: Import Form, Log Window, Animation, Warning #}
        <div class="col-md-3 mb-4">
            <div class="card">
                <div class="card-body">
                    {# Import Form #}
                    <h2><i class="bi bi-download me-2"></i>Import a New IG</h2>
                    <form id="import-ig-form"> {# Form ID used by JS #}
                        {{ form.hidden_tag() }} {# Include CSRF token if using Flask-WTF #}
                        {{ render_field(form.package_name, class="form-control") }}
                        {{ render_field(form.package_version, class="form-control") }}
                        {{ render_field(form.dependency_mode, class="form-select") }}
                        <div class="d-grid gap-2 d-sm-flex mt-3">
                             {# Import Button triggers HTMX POST #}
                            <button class="btn btn-success" id="submit-btn"
                                    hx-post="{{ url_for('import_ig') }}"
                                    hx-indicator="#import-spinner"
                                    hx-include="closest form"
                                    hx-swap="none">
                                Import
                            </button>
                            <span id="import-spinner" class="spinner-border spinner-border-sm align-middle ms-2" role="status" aria-hidden="true" style="display: none;"></span>
                            <a href="{{ url_for('index') }}" class="btn btn-secondary">Back</a> {# Simple link back #}
                        </div>
                    </form>

                    {# Live Log Output Window #}
                    <div class="log-window mt-4">
                        <h5><i class="bi bi-terminal me-2"></i>Live Log Output</h5>
                        <div id="log-output" class="log-output">
                            <p class="text-muted small">Logs from caching or import actions will appear here.</p>
                        </div>
                        {# Indicator shown while connecting to SSE #}
                        <div id="log-loading-indicator" class="mt-2 text-muted small" style="display: none;">
                            <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Connecting to log stream...
                        </div>
                    </div>

                    {# Animation Window (Hidden by default) #}
                    <div id="log-animation-window" class="minifire-container" style="display: none;">
                        {# Status Text Overlay #}
                        <div id="animation-status-text" class="minifire-status-text"></div>
                        {# Campfire Animation HTML Structure #}
                        <div class="minifire-campfire">
                            <div class="minifire-sparks">
                                <div class="minifire-spark"></div> <div class="minifire-spark"></div> <div class="minifire-spark"></div> <div class="minifire-spark"></div>
                                <div class="minifire-spark"></div> <div class="minifire-spark"></div> <div class="minifire-spark"></div> <div class="minifire-spark"></div>
                            </div>
                            <div class="minifire-logs">
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                                <div class="minifire-log"><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div><div class="minifire-streak"></div></div>
                            </div>
                            <div class="minifire-sticks">
                                <div class="minifire-stick"></div> <div class="minifire-stick"></div> <div class="minifire-stick"></div> <div class="minifire-stick"></div>
                            </div>
                            <div class="minifire-fire">
                                <div class="minifire-fire__red"> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> </div>
                                <div class="minifire-fire__orange"> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> </div>
                                <div class="minifire-fire__yellow"> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> </div>
                                <div class="minifire-fire__white"> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> <div class="minifire-flame"></div> </div>
                            </div>
                        </div>
                    </div>

                    {# Warning Text (Hidden by default) #}
                    <div id="process-warning-text" class="import-warning-text" style="display: none; margin-top: 1rem;">
                        DO NOT LEAVE PAGE<br>
                        UNTIL COMPLETED<br>
                        !ANIMATION DISAPEARS!
                    </div>

                </div> {# End card-body #}
            </div> {# End card #}
        </div> {# End col-md-3 #}
    </div> {# End row #}
</div> {# End container #}

<style>
/* Internal CSS Styles */
.log-window { margin-top: 1.5rem; }
.log-output {
    background-color: #1e2228; border: 1px solid #444; border-radius: 4px; padding: 10px;
    height: 250px; overflow-y: auto; font-family: 'Courier New', Courier, monospace;
    font-size: 0.85rem; color: #e0e0e0; white-space: pre-wrap; word-wrap: break-word;
}
.log-output p { margin: 0; padding: 1px 0; border-bottom: 1px solid #333; line-height: 1.3; }
.log-output p:last-child { border-bottom: none; }
.log-output .log-timestamp { color: #777; margin-right: 8px; }
.log-output .text-muted.small { color: #888 !important; font-style: italic; border-bottom: none; }

/* Animation Status Text (from fire-animation.css or here) */
.minifire-status-text {
    position: absolute; top: 8px; left: 0; width: 100%; text-align: center;
    color: #f0f0f0; font-size: 0.85em; font-weight: bold; z-index: 60;
    pointer-events: none; text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8); line-height: 1.2;
}

/* Warning Text Style */
.import-warning-text {
    color: #dc3545; /* Bootstrap danger color */
    font-style: italic;
    font-weight: bold;
    text-align: center;
    font-size: 0.9em;
    line-height: 1.3;
}

/* Other general styles */
.text-muted { color: #ccc !important; font-size: 1.1rem; margin-top: 1rem; }
.table, .card { background-color: #2a2e34; color: #fff; }
.table th, .table td { border-color: #444; }
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline-block; opacity: 1; }
#cache-spinner, #import-spinner { vertical-align: text-bottom; }
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
    // Check if HTMX is loaded (essential for button actions)
    if (typeof htmx === 'undefined') {
        console.error('HTMX is not loaded. Button functionality will be disabled.');
        const searchErrorDiv = document.getElementById('search-error');
        if(searchErrorDiv) {
            searchErrorDiv.textContent = 'Error: Core page functionality failed to load. Refresh or contact support.';
            searchErrorDiv.classList.remove('d-none');
        }
        // Disable interactive elements that rely on HTMX
        document.getElementById('clear-cache-btn')?.setAttribute('disabled', 'true');
        document.getElementById('submit-btn')?.setAttribute('disabled', 'true');
        document.querySelector('input[name="search"]')?.setAttribute('disabled', 'true');
        return; // Stop script execution
    }

    // Get references to frequently used DOM elements
    const logOutput = document.getElementById('log-output');
    const logLoadingIndicator = document.getElementById('log-loading-indicator');
    const logAnimationWindow = document.getElementById('log-animation-window');
    const animationStatusText = document.getElementById('animation-status-text');
    const processWarningText = document.getElementById('process-warning-text'); // Get warning text element
    let currentEventSource = null; // Variable to hold the active Server-Sent Events connection

    // --- Helper Functions ---

    // Appends a formatted log message to the log output window
    function appendLogMessage(message) {
        if (!logOutput) return;
        const initialMsg = logOutput.querySelector('.text-muted.small'); // Find placeholder
        if (initialMsg) initialMsg.remove(); // Remove placeholder on first message

        const timestamp = new Date().toLocaleTimeString(); // Get current time for timestamp
        const logEntry = document.createElement('p'); // Create paragraph for the log line
        const messageText = String(message).replace(/^INFO:services:/, '').replace(/^INFO:app:/, '').trim(); // Clean message

        logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span>`; // Add timestamp
        logEntry.appendChild(document.createTextNode(messageText)); // Add log message text safely

        // Style error/warning messages
        if (messageText.startsWith("ERROR:") || messageText.startsWith("CRITICAL ERROR:")) {
            logEntry.style.color = "#f8d7da"; logEntry.style.backgroundColor = "#49272a";
        } else if (messageText.startsWith("WARNING:")) {
            logEntry.style.color = "#fff3cd";
        }

        logOutput.appendChild(logEntry); // Add the log line to the window
        logOutput.scrollTop = logOutput.scrollHeight; // Scroll to the bottom
    }

    // Clears the log window and resets the placeholder text
    function clearLogWindow() {
        if (logOutput) logOutput.innerHTML = '<p class="text-muted small">Waiting for logs...</p>';
    }

    // Shows the animation window and sets the status text
    function showAnimation(statusText = '') {
        if (logAnimationWindow) logAnimationWindow.style.display = 'flex'; // Use flex to utilize justify/align center for campfire
        if (animationStatusText) animationStatusText.textContent = statusText; // Update status text
        if (processWarningText) processWarningText.style.display = 'block'; // Show the warning text
    }

    // Hides the animation window and clears status/warning text
    function hideAnimation() {
        if (logAnimationWindow) logAnimationWindow.style.display = 'none';
        if (animationStatusText) animationStatusText.textContent = '';
        if (processWarningText) processWarningText.style.display = 'none'; // Hide the warning text
    }

    // --- Server-Sent Events (SSE) Setup Function ---
    // Connects to the log stream and handles incoming messages
     function setupLogStream(onDoneCallback) {
         if (currentEventSource) { currentEventSource.close(); currentEventSource = null; } // Close existing connection
         clearLogWindow(); // Clear previous logs
         if (logLoadingIndicator) logLoadingIndicator.style.display = 'block'; // Show "Connecting..."
         // Note: showAnimation() is now called by the function initiating the action

         try {
             currentEventSource = new EventSource("{{ url_for('stream_import_logs') }}"); // Establish connection

             // On successful connection opening
             currentEventSource.onopen = function() {
                 console.log("SSE connection opened.");
                 if (logLoadingIndicator) logLoadingIndicator.style.display = 'none'; // Hide "Connecting..."
                 // Animation should already be visible here if started correctly
             };

             // On receiving a message from the server
             currentEventSource.onmessage = function(event) {
                 if (logLoadingIndicator) logLoadingIndicator.style.display = 'none'; // Hide indicator if still visible
                 // Keep animation visible

                 // Check for the special [DONE] signal
                 if (event.data === '[DONE]') {
                     console.log("SSE stream finished ([DONE] received).");
                     hideAnimation(); // Hide animation and warning text
                     const lastLog = logOutput.lastElementChild?.textContent || ""; // Check last log for errors
                     const hadError = lastLog.includes("ERROR:") || lastLog.includes("CRITICAL ERROR:");
                     if (!hadError) { appendLogMessage("Operation complete."); } // Log completion if no errors seen

                     if (currentEventSource) { currentEventSource.close(); currentEventSource = null; } // Close SSE
                     if (onDoneCallback) onDoneCallback(true, hadError); // Trigger callback (success=true)
                 } else {
                     appendLogMessage(event.data); // Log the received message
                 }
             };

             // On SSE connection error
             currentEventSource.onerror = function(err) {
                 console.error('SSE connection error:', err);
                 if (logLoadingIndicator) logLoadingIndicator.style.display = 'none';
                 hideAnimation(); // Hide animation and warning text on error
                 appendLogMessage("ERROR: Log streaming connection error or closed prematurely.");
                 if (currentEventSource) { currentEventSource.close(); currentEventSource = null; }
                 if (onDoneCallback) onDoneCallback(false, true); // Trigger callback (success=false, error=true)
             };

         } catch (e) { // On error initializing EventSource
             console.error('Failed to initialize SSE:', e);
             if (logLoadingIndicator) logLoadingIndicator.style.display = 'none';
             hideAnimation(); // Hide animation and warning text on failure
             appendLogMessage("ERROR: Unable to establish log stream: " + (e.message || 'Unknown Error'));
             if (onDoneCallback) onDoneCallback(false, true); // Trigger callback (success=false, error=true)
         }
     }

    // --- Page Load / Initial State Logic ---

    // Check if the backend flagged that an initial fetch is happening
    const isFetchingInitial = {{ is_fetching | tojson }};
    if (isFetchingInitial) {
        console.log("Initial fetch detected, setting up log stream.");
        showAnimation('Refreshing cache...'); // Show animation and "Refreshing" text immediately
        setupLogStream(function(success, streamHadError) {
             console.log(`Initial fetch log stream ended. Success: ${success}, Stream Error: ${streamHadError}`);
             hideAnimation(); // Hide animation when stream ends
             // Backend handles the page update after its fetch finishes
         });
    }

    // --- Event Listeners for Buttons ---

    // Listener for "Clear & Refresh Cache" Button (HTMX Triggered)
    const clearCacheBtn = document.getElementById('clear-cache-btn');
    const cacheSpinner = document.getElementById('cache-spinner');
    if (clearCacheBtn) {
        // Before HTMX sends the request to start the cache refresh task
        clearCacheBtn.addEventListener('htmx:beforeRequest', function(evt) {
             console.log("HTMX Clear Cache request starting.");
             clearCacheBtn.disabled = true; if(cacheSpinner) cacheSpinner.style.display = 'inline-block';
             showAnimation('Refreshing cache...'); // Show animation and set text
             // Setup the log stream, providing the callback for when SSE finishes
             setupLogStream(function(success, streamHadError) {
                 // This runs ONLY when the SSE stream sends [DONE] or errors out
                 console.log(`Clear cache log stream ended. Success: ${success}, Stream Error: ${streamHadError}`);
                 hideAnimation(); // Hide animation and warning text
                 clearCacheBtn.disabled = false; if(cacheSpinner) cacheSpinner.style.display = 'none'; // Re-enable button/hide spinner

                 // Determine message and reload page
                 if (success && !streamHadError) { appendLogMessage("Cache refresh complete. Reloading page..."); }
                 else if (success && streamHadError) { appendLogMessage("Cache refresh finished with errors. Reloading page..."); }
                 else { appendLogMessage("ERROR: Cache refresh failed or log stream error. Reloading page..."); }
                 setTimeout(() => window.location.reload(), 750); // Reload after slight delay
             });
        });

        // After HTMX gets the *initial* response from the API (should be 202 Accepted)
        clearCacheBtn.addEventListener('htmx:afterRequest', function(evt) {
             console.log('HTMX Clear Cache API response received. Status:', evt.detail.xhr.status);
             if (evt.detail.failed || evt.detail.xhr.status >= 400) {
                  // The API call itself failed to start the task
                  hideAnimation(); // Hide animation/warning immediately
                  console.error("Failed to trigger cache refresh API:", evt.detail);
                  appendLogMessage(`ERROR: Could not start cache refresh (Server status: ${evt.detail.xhr.status})`);
                  clearCacheBtn.disabled = false; if(cacheSpinner) cacheSpinner.style.display = 'none'; // Reset button
                  // Close SSE stream if it was prematurely opened
                  if (currentEventSource) { currentEventSource.close(); currentEventSource = null; appendLogMessage("Log stream closed."); }
             } else if (evt.detail.xhr.status === 202) {
                 // Success: Task started in background
                 appendLogMessage("Cache refresh process started in background...");
                 // Keep animation and warning text visible, waiting for SSE logs
             } else {
                 // Unexpected success status from API
                 hideAnimation(); // Hide animation/warning
                 appendLogMessage(`Warning: Unexpected server response ${evt.detail.xhr.status}. Refresh may not have started.`);
                 clearCacheBtn.disabled = false; if(cacheSpinner) cacheSpinner.style.display = 'none';
                 if (currentEventSource) { currentEventSource.close(); currentEventSource = null; } // Close stream
             }
         });
    }

    // Listener for Import Button (HTMX Triggered)
    const importForm = document.getElementById('import-ig-form');
    const importSubmitBtn = document.getElementById('submit-btn');
    const importSpinner = document.getElementById('import-spinner');
     if (importForm && importSubmitBtn) {
         // Before HTMX sends the import request
         importSubmitBtn.addEventListener('htmx:beforeRequest', function(evt) {
             console.log('HTMX import request starting.');
             importSubmitBtn.disabled = true; if(importSpinner) importSpinner.style.display = 'inline-block';
             showAnimation('Importing...'); // Show animation and set "Importing..." text
             // Setup log stream, callback runs when SSE part finishes
             setupLogStream(function(success, streamHadError) {
                 console.log(`Import log stream ended. Success: ${success}, Stream Error: ${streamHadError}`);
                 hideAnimation(); // Hide animation/warning when SSE finishes
                 // Note: Redirect or final status is handled in 'afterRequest' below
                 if (!success || streamHadError) { appendLogMessage("Warning: Log stream for import finished with errors or failed."); }
             });
         });

         // After HTMX gets the response from the import API call
          importSubmitBtn.addEventListener('htmx:afterRequest', function(evt) {
              console.log('HTMX import request finished. Status:', evt.detail.xhr.status);
              importSubmitBtn.disabled = false; if(importSpinner) importSpinner.style.display = 'none'; // Reset button/spinner

              // Ensure animation/warning are hidden if the main request fails quickly
              if (evt.detail.failed) hideAnimation();

              // Check SSE state (though it should manage its own closure)
              if (currentEventSource) console.warn("SSE connection might still be open after import HTMX request completed.");

              // Handle failed import API request
              if (evt.detail.failed) {
                  appendLogMessage(`ERROR: Import request failed (Status: ${evt.detail.xhr.status})`);
                  try { const errorData = JSON.parse(evt.detail.xhr.responseText); if (errorData?.message) appendLogMessage(` -> ${errorData.message}`); } catch(e) {}
              } else {
                  // Handle successful import API request (parse JSON response for status/redirect)
                  try {
                      const data = JSON.parse(evt.detail.xhr.responseText);
                      console.log("Import response data:", data);

                      // --- REDIRECT/STATUS LOGIC ---
                      if (data?.status === 'success' && data.redirect) {
                          appendLogMessage("Import successful. Redirecting...");
                          setTimeout(() => { window.location.href = data.redirect; }, 1000); // Redirect after 1s
                      } else if (data?.status === 'warning') {
                          appendLogMessage(`Warning: ${data.message || 'Import partially successful.'}`); htmx.process(document.body); // Process flash messages if swapped
                      } else if (data?.status === 'error') {
                           appendLogMessage(`ERROR: ${data.message || 'Import failed.'}`); htmx.process(document.body); // Process flash messages if swapped
                      } else {
                          appendLogMessage("Import request completed (unexpected response format)."); htmx.process(document.body); // Process flash messages if swapped
                      }
                      // --- END REDIRECT/STATUS LOGIC ---

                  } catch (e) { // Handle JSON parsing error
                      console.error("Error parsing import response JSON:", e);
                      appendLogMessage("ERROR: Could not process server response after import.");
                  }
              }
          });
     }

    // --- HTMX Search Event Listener (No changes needed) ---
    htmx.on('htmx:afterRequest', function(evt) {
        if (evt.detail.requestConfig?.path?.includes('api/search-packages')) { /* ... existing search error handling ... */ }
    });
    htmx.on('htmx:afterSwap', function(evt) { console.log("HTMX content swapped for target:", evt.detail.target.id); });

    // --- Page Unload Cleanup ---
    // Close SSE connection if user navigates away or closes tab
    window.addEventListener('beforeunload', () => {
        if (currentEventSource) {
            console.log("Page unloading, closing SSE connection.");
            currentEventSource.close();
            currentEventSource = null;
        }
    });

}); // End DOMContentLoaded
</script>
{% endblock %}