{% extends "base.html" %}

{% block title %}Sound Level Meters - Seismo Fleet Manager{% endblock %}

{% block content %}
{% include "partials/fleet_tab_strip.html" %}
<div class="mb-8">
    <h1 class="text-3xl font-bold text-gray-900 dark:text-white flex items-center">
        <svg class="w-8 h-8 mr-3 text-seismo-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"></path>
        </svg>
        Sound Level Meters
    </h1>
    <p class="text-gray-600 dark:text-gray-400 mt-1">Monitor and control sound level measurement devices</p>
</div>

<!-- Summary Stats -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8"
     hx-get="/api/slm-dashboard/stats"
     hx-trigger="load, every 10s"
     hx-swap="innerHTML">
    <!-- Stats will be loaded here -->
    <div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
    <div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
    <div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
    <div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
</div>

<!-- Device List with Quick Actions -->
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 mb-8">
    <div class="flex items-center justify-between mb-6">
        <h2 class="text-xl font-semibold text-gray-900 dark:text-white">Active Devices</h2>
        <div class="flex items-center gap-3">
            <span class="text-sm text-gray-500 dark:text-gray-400">Auto-refresh: 15s</span>
            <a href="/roster" class="text-sm text-seismo-orange hover:text-seismo-navy">Manage roster</a>
        </div>
    </div>

    <div id="slm-devices-list"
         class="space-y-3"
         hx-get="/api/slm-dashboard/units?include_measurement=true"
         hx-trigger="load, every 15s"
         hx-swap="innerHTML">
        <div class="animate-pulse space-y-3">
            <div class="bg-gray-200 dark:bg-gray-700 h-32 rounded-lg"></div>
            <div class="bg-gray-200 dark:bg-gray-700 h-32 rounded-lg"></div>
            <div class="bg-gray-200 dark:bg-gray-700 h-32 rounded-lg"></div>
        </div>
    </div>
</div>

<!-- Live Measurement Chart - shows when a device is selected -->
<div id="live-chart-panel" class="hidden bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 mb-8">
    <div class="flex items-start justify-between mb-6">
        <div>
            <h2 class="text-xl font-semibold text-gray-900 dark:text-white">
                Live Measurements
                <span id="panel-unit-id" class="text-seismo-orange"></span>
            </h2>
            <!-- Measuring state + cache freshness (populated from cached /status, no device hit) -->
            <div class="mt-1 flex items-center gap-2 text-sm">
                <span id="panel-measuring-badge" class="hidden px-2 py-0.5 text-xs font-medium rounded-full"></span>
                <span id="panel-freshness" class="text-gray-500 dark:text-gray-400"></span>
            </div>
        </div>
        <div class="flex items-center gap-3">
            <button onclick="refreshDashboardPanel()" title="Refresh from device"
                    class="text-gray-500 hover:text-seismo-orange dark:text-gray-400 dark:hover:text-seismo-orange">
                <svg id="panel-refresh-icon" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
                </svg>
            </button>
            <button onclick="closeLiveChart()" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
                <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                </svg>
            </button>
        </div>
    </div>

    <!-- Current Metrics -->
    <div class="grid grid-cols-5 gap-4 mb-6">
        <div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
            <p class="text-xs text-gray-600 dark:text-gray-400 mb-1">Lp (Instant)</p>
            <p id="chart-lp" class="text-2xl font-bold text-blue-600 dark:text-blue-400">--</p>
            <p class="text-xs text-gray-500 dark:text-gray-400">dB</p>
        </div>

        <div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4">
            <p class="text-xs text-gray-600 dark:text-gray-400 mb-1">Leq (Average)</p>
            <p id="chart-leq" class="text-2xl font-bold text-green-600 dark:text-green-400">--</p>
            <p class="text-xs text-gray-500 dark:text-gray-400">dB</p>
        </div>

        <div class="bg-red-50 dark:bg-red-900/20 rounded-lg p-4">
            <p class="text-xs text-gray-600 dark:text-gray-400 mb-1">Lmax (Max)</p>
            <p id="chart-lmax" class="text-2xl font-bold text-red-600 dark:text-red-400">--</p>
            <p class="text-xs text-gray-500 dark:text-gray-400">dB</p>
        </div>

        <div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-4">
            <p id="chart-ln1-label" class="text-xs text-gray-600 dark:text-gray-400 mb-1">L1</p>
            <p id="chart-ln1" class="text-2xl font-bold text-purple-600 dark:text-purple-400">--</p>
            <p class="text-xs text-gray-500 dark:text-gray-400">dB</p>
        </div>

        <div class="bg-orange-50 dark:bg-orange-900/20 rounded-lg p-4">
            <p id="chart-ln2-label" class="text-xs text-gray-600 dark:text-gray-400 mb-1">L10</p>
            <p id="chart-ln2" class="text-2xl font-bold text-orange-600 dark:text-orange-400">--</p>
            <p class="text-xs text-gray-500 dark:text-gray-400">dB</p>
        </div>
    </div>

    <!-- Chart -->
    <div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4" style="min-height: 400px;">
        <canvas id="dashboardLiveChart"></canvas>
    </div>

    <!-- Stream Control -->
    <div class="mt-4 flex justify-center gap-3">
        <button id="start-chart-stream" onclick="startDashboardStream()"
                class="px-6 py-2 bg-seismo-orange hover:bg-orange-600 text-white rounded-lg flex items-center">
            <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
            </svg>
            Start Live Stream
        </button>
        <button id="stop-chart-stream" onclick="stopDashboardStream()" style="display: none;"
                class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center">
            <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"></path>
            </svg>
            Stop Live Stream
        </button>
    </div>
</div>

<!-- Projects Overview -->
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
    <div class="flex items-center justify-between mb-4">
        <h2 class="text-xl font-semibold text-gray-900 dark:text-white">Active Projects</h2>
        <a href="/projects" class="text-sm text-seismo-orange hover:text-seismo-navy">View all</a>
    </div>

    <div id="slm-projects-list"
         class="space-y-3 max-h-[400px] overflow-y-auto"
         hx-get="/api/projects/list?status=active&project_type_id=sound_monitoring&view=compact"
         hx-trigger="load, every 60s"
         hx-swap="innerHTML">
        <div class="animate-pulse space-y-3">
            <div class="bg-gray-200 dark:bg-gray-700 h-20 rounded-lg"></div>
            <div class="bg-gray-200 dark:bg-gray-700 h-20 rounded-lg"></div>
            <div class="bg-gray-200 dark:bg-gray-700 h-20 rounded-lg"></div>
        </div>
    </div>
</div>

<!-- Unified SLM Settings Modal -->
{% include 'partials/slm_settings_modal.html' %}

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Global variables
window.dashboardChart = null;
window.dashboardWebSocket = null;
window.selectedUnitId = null;
window.dashboardChartData = {
    timestamps: [],
    lp: [],
    leq: [],
    ln1: [],
    ln2: []
};

// Parse a metric to a number, or null (so a missing/"-.-" percentile leaves a gap
// in the line instead of dropping it to 0).
function numOrNull(v) {
    const f = parseFloat(v);
    return isNaN(f) ? null : f;
}

// Initialize Chart.js
function initializeDashboardChart() {
    if (typeof Chart === 'undefined') {
        setTimeout(initializeDashboardChart, 100);
        return;
    }

    const canvas = document.getElementById('dashboardLiveChart');
    if (!canvas) return;

    if (window.dashboardChart) {
        window.dashboardChart.destroy();
    }

    const ctx = canvas.getContext('2d');
    const isDarkMode = document.documentElement.classList.contains('dark');
    const gridColor = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
    const textColor = isDarkMode ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)';

    window.dashboardChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: [],
            datasets: [
                {
                    label: 'Lp (Instantaneous)',
                    data: [],
                    borderColor: 'rgb(59, 130, 246)',
                    backgroundColor: 'rgba(59, 130, 246, 0.1)',
                    tension: 0.3,
                    borderWidth: 2,
                    pointRadius: 0
                },
                {
                    label: 'Leq (Equivalent)',
                    data: [],
                    borderColor: 'rgb(34, 197, 94)',
                    backgroundColor: 'rgba(34, 197, 94, 0.1)',
                    tension: 0.3,
                    borderWidth: 2,
                    pointRadius: 0
                },
                {
                    label: 'L1',
                    data: [],
                    borderColor: 'rgb(168, 85, 247)',
                    backgroundColor: 'rgba(168, 85, 247, 0.1)',
                    tension: 0.3,
                    borderWidth: 2,
                    pointRadius: 0,
                    spanGaps: true
                },
                {
                    label: 'L10',
                    data: [],
                    borderColor: 'rgb(249, 115, 22)',
                    backgroundColor: 'rgba(249, 115, 22, 0.1)',
                    tension: 0.3,
                    borderWidth: 2,
                    pointRadius: 0,
                    spanGaps: true
                }
            ]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: false,
            interaction: {
                intersect: false,
                mode: 'index'
            },
            scales: {
                x: {
                    display: true,
                    grid: { color: gridColor },
                    ticks: { color: textColor, maxTicksLimit: 10 }
                },
                y: {
                    display: true,
                    title: {
                        display: true,
                        text: 'Sound Level (dB)',
                        color: textColor
                    },
                    grid: { color: gridColor },
                    ticks: { color: textColor },
                    min: 30,
                    max: 130
                }
            },
            plugins: {
                legend: {
                    labels: { color: textColor }
                }
            }
        }
    });
}

// Show live chart for a specific unit
function showLiveChart(unitId) {
    window.selectedUnitId = unitId;
    const panel = document.getElementById('live-chart-panel');
    panel.classList.remove('hidden');

    // Initialize chart if needed
    if (!window.dashboardChart) {
        initializeDashboardChart();
    }

    // Reset data for the newly-selected unit (clears any prior unit's line)
    window.dashboardChartData = { timestamps: [], lp: [], leq: [], ln1: [], ln2: [] };
    if (window.dashboardChart) {
        window.dashboardChart.data.labels = [];
        window.dashboardChart.data.datasets.forEach(ds => ds.data = []);
        window.dashboardChart.update('none');
    }

    // Name the unit; clear stale status until the cache read returns
    const unitLabel = document.getElementById('panel-unit-id');
    if (unitLabel) unitLabel.textContent = '· ' + unitId;
    setPanelStatus(null, null);

    // Populate immediately from CACHE (no device hit): KPI cards + chart trail.
    prefillDashboardPanel(unitId);
    backfillDashboardChart(unitId);
    // Keep the cards updating from cache (~15s) without opening a device stream.
    startPanelCachePolling(unitId);

    // Scroll to chart
    panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

function closeLiveChart() {
    stopDashboardStream();
    stopPanelCachePolling();
    document.getElementById('live-chart-panel').classList.add('hidden');
    window.selectedUnitId = null;
}

// WebSocket streaming
function startDashboardStream() {
    if (!window.selectedUnitId) return;

    // Close existing connection
    if (window.dashboardWebSocket) {
        window.dashboardWebSocket.close();
    }

    // The live WS takes over from the cache poller; keep the backfilled trail on
    // the chart so the live frames continue the line instead of blanking it.
    stopPanelCachePolling();

    const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = `${wsProtocol}//${window.location.host}/api/slmm/${window.selectedUnitId}/monitor`;

    window.dashboardWebSocket = new WebSocket(wsUrl);

    window.dashboardWebSocket.onopen = function() {
        console.log('Dashboard WebSocket connected');
        document.getElementById('start-chart-stream').style.display = 'none';
        document.getElementById('stop-chart-stream').style.display = 'flex';
    };

    window.dashboardWebSocket.onmessage = function(event) {
        try {
            const data = JSON.parse(event.data);
            // /monitor sends keepalive 'heartbeat' frames (no metrics) and a per-frame
            // 'feed_status'; skip heartbeats and offline frames so they don't blank the
            // metrics or spike the chart with zeros.
            if (data.heartbeat || data.feed_status === 'unreachable') return;
            updateDashboardMetrics(data);
            updateDashboardChart(data);
        } catch (error) {
            console.error('Error parsing WebSocket message:', error);
        }
    };

    window.dashboardWebSocket.onerror = function(error) {
        console.error('Dashboard WebSocket error:', error);
    };

    window.dashboardWebSocket.onclose = function() {
        console.log('Dashboard WebSocket closed');
        document.getElementById('start-chart-stream').style.display = 'flex';
        document.getElementById('stop-chart-stream').style.display = 'none';
    };
}

function stopDashboardStream() {
    if (window.dashboardWebSocket) {
        window.dashboardWebSocket.close();
        window.dashboardWebSocket = null;
    }
    // Fall back to cache polling so the cards keep refreshing while the panel is open.
    if (window.selectedUnitId && !document.getElementById('live-chart-panel').classList.contains('hidden')) {
        startPanelCachePolling(window.selectedUnitId);
    }
}

function updateDashboardMetrics(data) {
    document.getElementById('chart-lp').textContent = data.lp || '--';
    document.getElementById('chart-leq').textContent = data.leq || '--';
    document.getElementById('chart-lmax').textContent = data.lmax || '--';
    // Guard: DRD stream frames omit percentiles, so only overwrite when present
    // (else the live stream blanks L1/L10 over the cached DOD snapshot values).
    if (data.ln1 != null) document.getElementById('chart-ln1').textContent = data.ln1;
    if (data.ln2 != null) document.getElementById('chart-ln2').textContent = data.ln2;
    if (data.ln1_label) document.getElementById('chart-ln1-label').textContent = data.ln1_label;
    if (data.ln2_label) document.getElementById('chart-ln2-label').textContent = data.ln2_label;
}

function updateDashboardChart(data) {
    const cd = window.dashboardChartData;
    const now = new Date();
    cd.timestamps.push(now.toLocaleTimeString());
    cd.lp.push(numOrNull(data.lp));
    cd.leq.push(numOrNull(data.leq));
    // /monitor (DOD) frames carry ln1/ln2; a DRD frame would omit them -> null gap.
    cd.ln1.push(numOrNull(data.ln1));
    cd.ln2.push(numOrNull(data.ln2));

    // Keep a generous window (backfill seeds up to ~120 points from the 2h trail).
    if (cd.timestamps.length > 600) {
        cd.timestamps.shift();
        cd.lp.shift();
        cd.leq.shift();
        cd.ln1.shift();
        cd.ln2.shift();
    }

    if (window.dashboardChart) {
        window.dashboardChart.data.labels = cd.timestamps;
        window.dashboardChart.data.datasets[0].data = cd.lp;
        window.dashboardChart.data.datasets[1].data = cd.leq;
        window.dashboardChart.data.datasets[2].data = cd.ln1;
        window.dashboardChart.data.datasets[3].data = cd.ln2;
        window.dashboardChart.update('none');
    }
}

// ---- Cached-data panel population (no device hit) -----------------------

// Fill the KPI cards + measuring/freshness from the cached NL43Status snapshot.
async function prefillDashboardPanel(unitId) {
    try {
        const r = await fetch(`/api/slmm/${encodeURIComponent(unitId)}/status`);
        if (!r.ok) {                       // 404 = device has never reported yet
            setPanelStatus(null, null);
            return;
        }
        const d = (await r.json()).data || {};
        updateDashboardMetrics(d);         // lp/leq/lmax/ln1/ln2 (ln guards keep cached percentiles)
        const measuring = d.measurement_state === 'Start' || d.measurement_state === 'Measure';
        setPanelStatus(measuring, d.last_seen);
    } catch (e) {
        console.warn('Panel cache prefill failed:', e);
    }
}

// Seed the chart from the downsampled DOD trail so it shows recent trend on open.
async function backfillDashboardChart(unitId) {
    try {
        const r = await fetch(`/api/slmm/${encodeURIComponent(unitId)}/history?hours=2`);
        if (!r.ok) return;
        const readings = (await r.json()).readings || [];
        const cd = window.dashboardChartData;
        if (!cd) return;
        for (const row of readings) {
            // Trail timestamps are naive UTC; append 'Z' to render in local time
            // consistently with the live frames (which use local Date.now()).
            cd.timestamps.push(row.timestamp ? new Date(row.timestamp + 'Z').toLocaleTimeString() : '');
            cd.lp.push(numOrNull(row.lp));
            cd.leq.push(numOrNull(row.leq));
            cd.ln1.push(numOrNull(row.ln1));
            cd.ln2.push(numOrNull(row.ln2));
        }
        if (window.dashboardChart) {
            window.dashboardChart.data.labels = cd.timestamps;
            window.dashboardChart.data.datasets[0].data = cd.lp;
            window.dashboardChart.data.datasets[1].data = cd.leq;
            window.dashboardChart.data.datasets[2].data = cd.ln1;
            window.dashboardChart.data.datasets[3].data = cd.ln2;
            window.dashboardChart.update('none');
        }
    } catch (e) {
        console.warn('Panel chart backfill failed:', e);
    }
}

// Measuring badge + "as of <time> (Xm ago)" freshness, so a cached value is never
// mistaken for a live one. measuring: true | false | null(unknown).
function setPanelStatus(measuring, lastSeenIso) {
    const badge = document.getElementById('panel-measuring-badge');
    const fresh = document.getElementById('panel-freshness');
    if (badge) {
        if (measuring === null) {
            badge.className = 'hidden px-2 py-0.5 text-xs font-medium rounded-full';
            badge.textContent = '';
        } else if (measuring) {
            badge.className = 'px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300';
            badge.textContent = '● Measuring';
        } else {
            badge.className = 'px-2 py-0.5 text-xs font-medium rounded-full bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300';
            badge.textContent = '■ Stopped';
        }
    }
    if (fresh) fresh.innerHTML = fmtFreshness(lastSeenIso);
}

// Human "x ago" with a staleness hint. Cached timestamps are naive UTC.
function fmtFreshness(lastSeenIso) {
    if (!lastSeenIso) return '<span class="text-gray-400">no cached reading yet</span>';
    const t = new Date(lastSeenIso.endsWith('Z') ? lastSeenIso : lastSeenIso + 'Z');
    const secs = Math.max(0, Math.round((Date.now() - t.getTime()) / 1000));
    let ago, stale = false;
    if (secs < 10) ago = 'just now';
    else if (secs < 60) ago = secs + 's ago';
    else if (secs < 3600) { ago = Math.round(secs / 60) + 'm ago'; stale = secs >= 300; }
    else { ago = Math.round(secs / 3600) + 'h ago'; stale = true; }
    const cls = stale ? 'text-amber-600 dark:text-amber-400' : 'text-gray-500 dark:text-gray-400';
    const tag = stale ? ' · cached' : '';
    return `as of ${t.toLocaleTimeString()} <span class="${cls}">(${ago}${tag})</span>`;
}

// Cache polling: refresh the cards from cache every 15s while the panel is open
// and not live-streaming. Pure cache reads — no device contention.
function startPanelCachePolling(unitId) {
    stopPanelCachePolling();
    window.panelCacheTimer = setInterval(() => {
        if (window.selectedUnitId) prefillDashboardPanel(window.selectedUnitId);
    }, 15000);
}
function stopPanelCachePolling() {
    if (window.panelCacheTimer) { clearInterval(window.panelCacheTimer); window.panelCacheTimer = null; }
}

// ---- On-demand device refresh (the per-unit + panel refresh buttons) -----

// One bounded, user-initiated device read: hits the device, updates the cache,
// returns the fresh data. Throws on unreachable/disabled.
async function forceDeviceRead(unitId) {
    const r = await fetch(`/api/slmm/${encodeURIComponent(unitId)}/live`);
    if (!r.ok) {
        let detail = 'device unreachable';
        try { detail = (await r.json()).detail || detail; } catch (e) {}
        throw new Error(detail);
    }
    return (await r.json()).data || {};
}

function spinIcon(el, on) {
    if (el) el.classList.toggle('animate-spin', on);
}

function applyFreshReadToPanel(unitId, d) {
    if (window.selectedUnitId !== unitId) return;
    updateDashboardMetrics(d);
    const measuring = d.measurement_state === 'Start' || d.measurement_state === 'Measure';
    // The read just happened, so "now" is the accurate freshness even if the
    // /live payload doesn't echo last_seen.
    setPanelStatus(measuring, d.last_seen || new Date().toISOString());
}

// Device-list row refresh button.
async function refreshSlmUnit(unitId, btn) {
    const icon = btn ? btn.querySelector('svg') : null;
    if (btn) btn.disabled = true;
    spinIcon(icon, true);
    try {
        const d = await forceDeviceRead(unitId);
        applyFreshReadToPanel(unitId, d);
        // Reload the list so the row's badge + last-check reflect the new cache.
        if (typeof htmx !== 'undefined' && document.getElementById('slm-devices-list')) {
            htmx.trigger('#slm-devices-list', 'load');
        }
        if (window.showToast) window.showToast(`${unitId} refreshed`, 'success');
    } catch (e) {
        if (window.showToast) window.showToast(`${unitId}: ${e.message}`, 'error');
        else console.warn('refresh failed', e);
    } finally {
        if (btn) btn.disabled = false;
        spinIcon(icon, false);
    }
}

// Panel header refresh button (refreshes the unit the panel is showing).
async function refreshDashboardPanel() {
    const unitId = window.selectedUnitId;
    if (!unitId) return;
    const icon = document.getElementById('panel-refresh-icon');
    spinIcon(icon, true);
    try {
        const d = await forceDeviceRead(unitId);
        applyFreshReadToPanel(unitId, d);
        updateDashboardChart(d);   // append the fresh point to the chart
        if (typeof htmx !== 'undefined' && document.getElementById('slm-devices-list')) {
            htmx.trigger('#slm-devices-list', 'load');
        }
        if (window.showToast) window.showToast(`${unitId} refreshed`, 'success');
    } catch (e) {
        if (window.showToast) window.showToast(`${unitId}: ${e.message}`, 'error');
    } finally {
        spinIcon(icon, false);
    }
}

// Configuration modal - use unified SLM settings modal
function openDeviceConfigModal(unitId) {
    // Call the unified modal function from slm_settings_modal.html
    if (typeof openSLMSettingsModal === 'function') {
        openSLMSettingsModal(unitId);
    } else {
        console.error('openSLMSettingsModal not found');
    }
}

function closeDeviceConfigModal() {
    // Call the unified modal close function
    if (typeof closeSLMSettingsModal === 'function') {
        closeSLMSettingsModal();
    }
}

// Cleanup on page unload
window.addEventListener('beforeunload', function() {
    stopDashboardStream();
});
</script>
{% endblock %}
