/**
 * Dashboard — real-time target status overview.
 */

import { apiKey, _dashboardLoading, set_dashboardLoading, dashboardPollInterval, setDashboardPollInterval, colorStripSourcesCache, devicesCache, outputTargetsCache } from '../core/state.js';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js';
import { t } from '../core/i18n.js';
import { showToast, showConfirm, formatUptime, formatCompact, setTabRefreshing } from '../core/ui.js';
import { renderPerfSection, initPerfCharts, startPerfPolling, stopPerfPolling } from './perf-charts.js';
import { startAutoRefresh, updateTabBadge } from './tabs.js';
import {
    ICON_TARGET, ICON_AUTOMATION, ICON_CLOCK, ICON_WARNING, ICON_OK,
    ICON_STOP, ICON_STOP_PLAIN, ICON_START, ICON_PAUSE, ICON_HELP, ICON_SCENE,
} from '../core/icons.js';
import { loadScenePresets, renderScenePresetsSection } from './scene-presets.js';
import { cardColorStyle } from '../core/card-colors.js';
import { createFpsSparkline } from '../core/chart-utils.js';

const DASHBOARD_COLLAPSED_KEY = 'dashboard_collapsed';
const MAX_FPS_SAMPLES = 120;

let _fpsHistory = {};                  // { targetId: number[] } — fps_actual
let _fpsCurrentHistory = {};           // { targetId: number[] } — fps_current
let _fpsCharts = {};                   // { targetId: Chart }
let _lastRunningIds = [];              // sorted target IDs from previous render
let _lastSyncClockIds = '';            // comma-joined sorted sync clock IDs
let _uptimeBase = {};                  // { targetId: { seconds, timestamp } }
let _uptimeTimer = null;
let _uptimeElements = {};              // { targetId: HTMLElement } — cached DOM refs
let _metricsElements = new Map();

function _pushFps(targetId, actual, current) {
    if (!_fpsHistory[targetId]) _fpsHistory[targetId] = [];
    _fpsHistory[targetId].push(actual);
    if (_fpsHistory[targetId].length > MAX_FPS_SAMPLES) _fpsHistory[targetId].shift();

    if (!_fpsCurrentHistory[targetId]) _fpsCurrentHistory[targetId] = [];
    _fpsCurrentHistory[targetId].push(current);
    if (_fpsCurrentHistory[targetId].length > MAX_FPS_SAMPLES) _fpsCurrentHistory[targetId].shift();
}

function _setUptimeBase(targetId, seconds) {
    _uptimeBase[targetId] = { seconds, timestamp: Date.now() };
}

function _getInterpolatedUptime(targetId) {
    const base = _uptimeBase[targetId];
    if (!base) return null;
    const elapsed = (Date.now() - base.timestamp) / 1000;
    return base.seconds + elapsed;
}

function _cacheUptimeElements() {
    _uptimeElements = {};
    for (const id of _lastRunningIds) {
        const el = document.querySelector(`[data-uptime-text="${id}"]`);
        if (el) _uptimeElements[id] = el;
    }
}

function _startUptimeTimer() {
    if (_uptimeTimer) return;
    _uptimeTimer = setInterval(() => {
        for (const id of _lastRunningIds) {
            const el = _uptimeElements[id];
            if (!el) continue;
            const seconds = _getInterpolatedUptime(id);
            if (seconds != null) {
                el.innerHTML = `${ICON_CLOCK} ${formatUptime(seconds)}`;
            }
        }
    }, 1000);
}

function _stopUptimeTimer() {
    if (_uptimeTimer) {
        clearInterval(_uptimeTimer);
        _uptimeTimer = null;
    }
    _uptimeBase = {};
    _uptimeElements = {};
}

function _destroyFpsCharts() {
    for (const id of Object.keys(_fpsCharts)) {
        if (_fpsCharts[id]) { _fpsCharts[id].destroy(); }
    }
    _fpsCharts = {};
}

function _createFpsChart(canvasId, actualHistory, currentHistory, fpsTarget) {
    return createFpsSparkline(canvasId, actualHistory, currentHistory, fpsTarget);
}

async function _initFpsCharts(runningTargetIds) {
    _destroyFpsCharts();

    // Seed FPS history from server ring buffer on first load
    if (Object.keys(_fpsHistory).length === 0 && runningTargetIds.length > 0) {
        try {
            const resp = await fetch(`${API_BASE}/system/metrics-history`, { headers: getHeaders() });
            if (resp.ok) {
                const data = await resp.json();
                const serverTargets = data.targets || {};
                for (const id of runningTargetIds) {
                    const samples = serverTargets[id] || [];
                    _fpsHistory[id] = samples.map(s => s.fps).filter(v => v != null);
                    _fpsCurrentHistory[id] = samples.map(s => s.fps_current).filter(v => v != null);
                }
            }
        } catch {
            // Silently ignore — charts will fill from polling
        }
    }

    // Clean up history for targets that are no longer running
    for (const id of Object.keys(_fpsHistory)) {
        if (!runningTargetIds.includes(id)) { delete _fpsHistory[id]; delete _fpsCurrentHistory[id]; }
    }
    for (const id of runningTargetIds) {
        const canvas = document.getElementById(`dashboard-fps-${id}`);
        if (!canvas) continue;
        const actualH = _fpsHistory[id] || [];
        const currentH = _fpsCurrentHistory[id] || [];
        const fpsTarget = parseFloat(canvas.dataset.fpsTarget) || 30;
        _fpsCharts[id] = _createFpsChart(`dashboard-fps-${id}`, actualH, currentH, fpsTarget);
    }

    _cacheMetricsElements(runningTargetIds);
}

function _cacheMetricsElements(runningIds) {
    _metricsElements.clear();
    for (const id of runningIds) {
        _metricsElements.set(id, {
            fps: document.querySelector(`[data-fps-text="${id}"]`),
            errors: document.querySelector(`[data-errors-text="${id}"]`),
            row: document.querySelector(`[data-target-id="${id}"]`),
        });
    }
}

/** Update running target metrics in-place (no HTML rebuild). */
function _updateRunningMetrics(enrichedRunning) {
    for (const target of enrichedRunning) {
        const state = target.state || {};
        const metrics = target.metrics || {};
        const fpsCurrent = state.fps_current ?? 0;
        const fpsActual = state.fps_actual != null ? state.fps_actual.toFixed(1) : '-';
        const fpsTarget = state.fps_target || (target.settings || target.key_colors_settings || {}).fps || '-';
        const errors = metrics.errors_count || 0;

        // Push FPS and update chart
        if (state.fps_actual != null) {
            _pushFps(target.id, state.fps_actual, fpsCurrent);
        }
        const chart = _fpsCharts[target.id];
        if (chart) {
            const actualH = _fpsHistory[target.id] || [];
            const currentH = _fpsCurrentHistory[target.id] || [];
            // Mutate in-place to avoid array copies
            const ds0 = chart.data.datasets[0].data;
            ds0.length = 0;
            ds0.push(...actualH);
            const ds1 = chart.data.datasets[1].data;
            ds1.length = 0;
            ds1.push(...currentH);
            while (chart.data.labels.length < ds0.length) chart.data.labels.push('');
            chart.data.labels.length = ds0.length;
            chart.update('none');
        }

        // Refresh uptime base for interpolation
        if (metrics.uptime_seconds != null) {
            _setUptimeBase(target.id, metrics.uptime_seconds);
        }

        // Update text values (use cached refs, fallback to querySelector)
        const cached = _metricsElements.get(target.id);
        const fpsEl = cached?.fps || document.querySelector(`[data-fps-text="${target.id}"]`);
        if (fpsEl) {
            const effFps = state.fps_effective;
            const fpsTargetLabel = (effFps != null && effFps < fpsTarget)
                ? `${fpsCurrent}<span class="dashboard-fps-target">/${effFps}↓${fpsTarget}</span>`
                : `${fpsCurrent}<span class="dashboard-fps-target">/${fpsTarget}</span>`;
            const unreachableClass = state.device_streaming_reachable === false ? ' fps-unreachable' : '';
            fpsEl.innerHTML = `<span class="${unreachableClass}">${fpsTargetLabel}</span>`
                + `<span class="dashboard-fps-avg">avg ${fpsActual}</span>`;
        }

        const errorsEl = cached?.errors || document.querySelector(`[data-errors-text="${target.id}"]`);
        if (errorsEl) { errorsEl.innerHTML = `${errors > 0 ? ICON_WARNING : ICON_OK} ${formatCompact(errors)}`; errorsEl.title = String(errors); }

        // Update health dot — prefer streaming reachability when processing
        const isLed = target.target_type === 'led' || target.target_type === 'wled';
        if (isLed) {
            const row = cached?.row || document.querySelector(`[data-target-id="${target.id}"]`);
            if (row) {
                const dot = row.querySelector('.health-dot');
                if (dot) {
                    const streamReachable = state.device_streaming_reachable;
                    if (state.processing && streamReachable != null) {
                        dot.className = `health-dot ${streamReachable ? 'health-online' : 'health-offline'}`;
                    } else if (state.device_last_checked != null) {
                        dot.className = `health-dot ${state.device_online ? 'health-online' : 'health-offline'}`;
                    }
                }
            }
        }
    }

}

function _updateAutomationsInPlace(automations) {
    for (const a of automations) {
        const card = document.querySelector(`[data-automation-id="${a.id}"]`);
        if (!card) continue;
        const badge = card.querySelector('.dashboard-badge-active, .dashboard-badge-stopped');
        if (badge) {
            if (!a.enabled) {
                badge.className = 'dashboard-badge-stopped';
                badge.textContent = t('automations.status.disabled');
            } else if (a.is_active) {
                badge.className = 'dashboard-badge-active';
                badge.textContent = t('automations.status.active');
            } else {
                badge.className = 'dashboard-badge-stopped';
                badge.textContent = t('automations.status.inactive');
            }
        }
        const btn = card.querySelector('.dashboard-target-actions .dashboard-action-btn');
        if (btn) {
            btn.className = `dashboard-action-btn ${a.enabled ? 'stop' : 'start'}`;
            btn.setAttribute('onclick', `dashboardToggleAutomation('${a.id}', ${!a.enabled})`);
            btn.innerHTML = a.enabled ? ICON_STOP_PLAIN : ICON_START;
        }
    }
}

function _updateSyncClocksInPlace(syncClocks) {
    for (const c of syncClocks) {
        const card = document.querySelector(`[data-sync-clock-id="${c.id}"]`);
        if (!card) continue;
        const speedEl = card.querySelector('.dashboard-clock-speed');
        if (speedEl) speedEl.textContent = `${c.speed}x`;
        const btn = card.querySelector('.dashboard-target-actions .dashboard-action-btn');
        if (btn) {
            btn.className = `dashboard-action-btn ${c.is_running ? 'stop' : 'start'}`;
            btn.setAttribute('onclick', c.is_running ? `dashboardPauseClock('${c.id}')` : `dashboardResumeClock('${c.id}')`);
            btn.innerHTML = c.is_running ? ICON_PAUSE : ICON_START;
        }
    }
}

function renderDashboardSyncClock(clock) {
    const toggleAction = clock.is_running
        ? `dashboardPauseClock('${clock.id}')`
        : `dashboardResumeClock('${clock.id}')`;
    const toggleTitle = clock.is_running ? t('sync_clock.action.pause') : t('sync_clock.action.resume');

    const subtitle = [
        `<span class="dashboard-clock-speed">${clock.speed}x</span>`,
        clock.description ? escapeHtml(clock.description) : '',
    ].filter(Boolean).join(' · ');

    const scStyle = cardColorStyle(clock.id);
    return `<div class="dashboard-target dashboard-autostart dashboard-card-link" data-sync-clock-id="${clock.id}" onclick="if(!event.target.closest('button')){navigateToCard('streams','sync','sync-clocks','data-id','${clock.id}')}"${scStyle ? ` style="${scStyle}"` : ''}>
        <div class="dashboard-target-info">
            <span class="dashboard-target-icon">${ICON_CLOCK}</span>
            <div>
                <div class="dashboard-target-name">${escapeHtml(clock.name)}</div>
                ${subtitle ? `<div class="dashboard-target-subtitle">${subtitle}</div>` : ''}
            </div>
        </div>
        <div class="dashboard-target-actions">
            <button class="dashboard-action-btn ${clock.is_running ? 'stop' : 'start'}" onclick="${toggleAction}" title="${toggleTitle}">
                ${clock.is_running ? ICON_PAUSE : ICON_START}
            </button>
            <button class="dashboard-action-btn" onclick="dashboardResetClock('${clock.id}')" title="${t('sync_clock.action.reset')}">
                ${ICON_CLOCK}
            </button>
        </div>
    </div>`;
}

function _renderPollIntervalSelect() {
    const sec = Math.round(dashboardPollInterval / 1000);
    return `<span class="dashboard-poll-wrap"><input type="range" class="dashboard-poll-slider" min="1" max="10" value="${sec}" oninput="changeDashboardPollInterval(this.value)" title="${t('dashboard.poll_interval')}"><span class="dashboard-poll-value">${sec}s</span></span>`;
}

let _pollDebounce = null;
export function changeDashboardPollInterval(value) {
    const label = document.querySelector('.dashboard-poll-value');
    if (label) label.textContent = `${value}s`;
    clearTimeout(_pollDebounce);
    _pollDebounce = setTimeout(() => {
        const ms = parseInt(value, 10) * 1000;
        setDashboardPollInterval(ms);
        startAutoRefresh();
        stopPerfPolling();
        startPerfPolling();
    }, 300);
}

function _getCollapsedSections() {
    try { return JSON.parse(localStorage.getItem(DASHBOARD_COLLAPSED_KEY)) || {}; }
    catch { return {}; }
}

export function toggleDashboardSection(sectionKey) {
    const collapsed = _getCollapsedSections();
    collapsed[sectionKey] = !collapsed[sectionKey];
    localStorage.setItem(DASHBOARD_COLLAPSED_KEY, JSON.stringify(collapsed));
    const header = document.querySelector(`[data-dashboard-section="${sectionKey}"]`);
    if (!header) return;
    const content = header.nextElementSibling;
    const chevron = header.querySelector('.dashboard-section-chevron');
    const nowCollapsed = collapsed[sectionKey];
    if (chevron) chevron.style.transform = nowCollapsed ? '' : 'rotate(90deg)';

    // Animate collapse/expand unless reduced motion
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
        content.style.display = nowCollapsed ? 'none' : '';
        return;
    }
    if (content._dsAnim) { content._dsAnim.cancel(); content._dsAnim = null; }
    if (nowCollapsed) {
        const h = content.offsetHeight;
        content.style.overflow = 'hidden';
        const anim = content.animate(
            [{ height: h + 'px', opacity: 1 }, { height: '0px', opacity: 0 }],
            { duration: 200, easing: 'ease-in-out' }
        );
        content._dsAnim = anim;
        anim.onfinish = () => { content.style.display = 'none'; content.style.overflow = ''; content._dsAnim = null; };
    } else {
        content.style.display = '';
        content.style.overflow = 'hidden';
        const h = content.scrollHeight;
        const anim = content.animate(
            [{ height: '0px', opacity: 0 }, { height: h + 'px', opacity: 1 }],
            { duration: 200, easing: 'ease-in-out' }
        );
        content._dsAnim = anim;
        anim.onfinish = () => { content.style.overflow = ''; content._dsAnim = null; };
    }
}

function _sectionHeader(sectionKey, label, count, extraHtml = '') {
    const collapsed = _getCollapsedSections();
    const isCollapsed = !!collapsed[sectionKey];
    const chevronStyle = isCollapsed ? '' : ' style="transform:rotate(90deg)"';
    return `<div class="dashboard-section-header" data-dashboard-section="${sectionKey}" onclick="toggleDashboardSection('${sectionKey}')">
        <span class="dashboard-section-chevron"${chevronStyle}>&#9654;</span>
        ${label}
        <span class="dashboard-section-count">${count}</span>
        ${extraHtml}
    </div>`;
}

function _sectionContent(sectionKey, itemsHtml) {
    const collapsed = _getCollapsedSections();
    const isCollapsed = !!collapsed[sectionKey];
    return `<div class="dashboard-section-content"${isCollapsed ? ' style="display:none"' : ''}>${itemsHtml}</div>`;
}

export async function loadDashboard(forceFullRender = false) {
    if (_dashboardLoading) return;
    set_dashboardLoading(true);
    const container = document.getElementById('dashboard-content');
    if (!container) { set_dashboardLoading(false); return; }
    if (!container.children.length) setTabRefreshing('dashboard-content', true);

    try {
        // Fire all requests in a single batch to avoid sequential RTTs
        const [targets, automationsResp, devicesArr, cssArr, batchStatesResp, batchMetricsResp, scenePresets, syncClocksResp] = await Promise.all([
            outputTargetsCache.fetch().catch(() => []),
            fetchWithAuth('/automations').catch(() => null),
            devicesCache.fetch().catch(() => []),
            colorStripSourcesCache.fetch().catch(() => []),
            fetchWithAuth('/output-targets/batch/states').catch(() => null),
            fetchWithAuth('/output-targets/batch/metrics').catch(() => null),
            loadScenePresets(),
            fetchWithAuth('/sync-clocks').catch(() => null),
        ]);

        const automationsData = automationsResp && automationsResp.ok ? await automationsResp.json() : { automations: [] };
        const automations = automationsData.automations || [];
        const devicesMap = {};
        for (const d of devicesArr) { devicesMap[d.id] = d; }
        const cssSourceMap = {};
        for (const s of (cssArr || [])) { cssSourceMap[s.id] = s; }
        const syncClocksData = syncClocksResp && syncClocksResp.ok ? await syncClocksResp.json() : { clocks: [] };
        const syncClocks = syncClocksData.clocks || [];

        const allStates = batchStatesResp && batchStatesResp.ok ? (await batchStatesResp.json()).states : {};
        const allMetrics = batchMetricsResp && batchMetricsResp.ok ? (await batchMetricsResp.json()).metrics : {};

        // Build dynamic HTML (targets, automations)
        let dynamicHtml = '';
        let runningIds = [];
        if (targets.length === 0 && automations.length === 0 && scenePresets.length === 0 && syncClocks.length === 0) {
            dynamicHtml = `<div class="dashboard-no-targets">${t('dashboard.no_targets')}</div>`;
        } else {
            const enriched = targets.map(target => ({
                ...target,
                state: allStates[target.id] || {},
                metrics: allMetrics[target.id] || {},
            }));

            const running = enriched.filter(t => t.state && t.state.processing);
            const stopped = enriched.filter(t => !t.state || !t.state.processing);
            updateTabBadge('targets', running.length);

            // Check if we can do an in-place metrics update (same targets, not first load)
            const newRunningIds = running.map(t => t.id).sort().join(',');
            const prevRunningIds = [..._lastRunningIds].sort().join(',');
            const newSyncClockIds = syncClocks.map(c => `${c.id}:${c.is_running}`).sort().join(',');
            const hasExistingDom = !!container.querySelector('.dashboard-perf-persistent');
            const structureUnchanged = hasExistingDom && newRunningIds === prevRunningIds && newSyncClockIds === _lastSyncClockIds;
            if (structureUnchanged && !forceFullRender && running.length > 0) {
                _updateRunningMetrics(running);
                _updateSyncClocksInPlace(syncClocks);
                _cacheUptimeElements();
                _startUptimeTimer();
                startPerfPolling();
                set_dashboardLoading(false);
                return;
            }
            if (structureUnchanged && forceFullRender) {
                if (running.length > 0) _updateRunningMetrics(running);
                _updateAutomationsInPlace(automations);
                _updateSyncClocksInPlace(syncClocks);
                _cacheUptimeElements();
                _startUptimeTimer();
                startPerfPolling();
                set_dashboardLoading(false);
                return;
            }

            if (automations.length > 0) {
                const activeAutomations = automations.filter(a => a.is_active);
                const inactiveAutomations = automations.filter(a => !a.is_active);
                updateTabBadge('automations', activeAutomations.length);
                const sceneMap = new Map(scenePresets.map(s => [s.id, s]));
                const automationItems = [...activeAutomations, ...inactiveAutomations].map(a => renderDashboardAutomation(a, sceneMap)).join('');

                dynamicHtml += `<div class="dashboard-section">
                    ${_sectionHeader('automations', t('dashboard.section.automations'), automations.length)}
                    ${_sectionContent('automations', automationItems)}
                </div>`;
            }

            // Scene Presets section
            if (scenePresets.length > 0) {
                const sceneSec = renderScenePresetsSection(scenePresets);
                if (sceneSec) {
                    dynamicHtml += `<div class="dashboard-section">
                        ${_sectionHeader('scenes', t('dashboard.section.scenes'), scenePresets.length, sceneSec.headerExtra)}
                        ${_sectionContent('scenes', sceneSec.content)}
                    </div>`;
                }
            }

            // Sync Clocks section
            if (syncClocks.length > 0) {
                const clockCards = syncClocks.map(c => renderDashboardSyncClock(c)).join('');
                const clockGrid = `<div class="dashboard-autostart-grid">${clockCards}</div>`;
                dynamicHtml += `<div class="dashboard-section">
                    ${_sectionHeader('sync-clocks', t('dashboard.section.sync_clocks'), syncClocks.length)}
                    ${_sectionContent('sync-clocks', clockGrid)}
                </div>`;
            }

            if (targets.length > 0) {
                let targetsInner = '';

                if (running.length > 0) {
                    runningIds = running.map(t => t.id);
                    const stopAllBtn = `<button class="btn btn-sm btn-primary dashboard-stop-all" onclick="event.stopPropagation(); dashboardStopAll()" title="${t('dashboard.stop_all')}">${ICON_STOP} ${t('dashboard.stop_all')}</button>`;
                    const runningItems = running.map(target => renderDashboardTarget(target, true, devicesMap, cssSourceMap)).join('');

                    targetsInner += `<div class="dashboard-subsection">
                        ${_sectionHeader('running', t('dashboard.section.running'), running.length, stopAllBtn)}
                        ${_sectionContent('running', runningItems)}
                    </div>`;
                }

                if (stopped.length > 0) {
                    const stoppedItems = stopped.map(target => renderDashboardTarget(target, false, devicesMap, cssSourceMap)).join('');

                    targetsInner += `<div class="dashboard-subsection">
                        ${_sectionHeader('stopped', t('dashboard.section.stopped'), stopped.length)}
                        ${_sectionContent('stopped', stoppedItems)}
                    </div>`;
                }

                dynamicHtml += `<div class="dashboard-section">
                    ${_sectionHeader('targets', t('dashboard.section.targets'), targets.length)}
                    ${_sectionContent('targets', targetsInner)}
                </div>`;
            }
        }

        // First load: build everything in one innerHTML to avoid flicker
        const isFirstLoad = !container.querySelector('.dashboard-perf-persistent');
        const pollSelect = _renderPollIntervalSelect();
        const toolbar = `<div class="stream-tab-bar"><span class="cs-expand-collapse-group">${pollSelect}<button class="tutorial-trigger-btn" onclick="startDashboardTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
        if (isFirstLoad) {
            container.innerHTML = `${toolbar}<div class="dashboard-perf-persistent dashboard-section">
                ${_sectionHeader('perf', t('dashboard.section.performance'), '')}
                ${_sectionContent('perf', renderPerfSection())}
            </div>
            <div class="dashboard-dynamic">${dynamicHtml}</div>`;
            await initPerfCharts();
        } else {
            const dynamic = container.querySelector('.dashboard-dynamic');
            if (dynamic.innerHTML !== dynamicHtml) {
                dynamic.innerHTML = dynamicHtml;
            }
        }
        _lastRunningIds = runningIds;
        _lastSyncClockIds = syncClocks.map(c => `${c.id}:${c.is_running}`).sort().join(',');
        _cacheUptimeElements();
        await _initFpsCharts(runningIds);
        _startUptimeTimer();
        startPerfPolling();

    } catch (error) {
        if (error.isAuth) return;
        console.error('Failed to load dashboard:', error);
        container.innerHTML = `<div class="dashboard-no-targets">${t('dashboard.failed')}</div>`;
    } finally {
        set_dashboardLoading(false);
        setTabRefreshing('dashboard-content', false);
    }
}

function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap = {}) {
    const state = target.state || {};
    const metrics = target.metrics || {};
    const isLed = target.target_type === 'led' || target.target_type === 'wled';
    const icon = ICON_TARGET;
    const typeLabel = isLed ? t('dashboard.type.led') : t('dashboard.type.kc');
    const navSubTab = isLed ? 'led-targets' : 'kc-targets';
    const navSection = isLed ? 'led-targets' : 'kc-targets';
    const navAttr = isLed ? 'data-target-id' : 'data-kc-target-id';
    const navOnclick = `if(!event.target.closest('button')){navigateToCard('targets','${navSubTab}','${navSection}','${navAttr}','${target.id}')}`;

    let subtitleParts = [typeLabel];
    if (isLed) {
        const device = target.device_id ? devicesMap[target.device_id] : null;
        if (device) {
            subtitleParts.push((device.device_type || '').toUpperCase());
        }
        const cssId = target.color_strip_source_id || '';
        if (cssId) {
            const css = cssSourceMap[cssId];
            if (css) {
                subtitleParts.push(t(`color_strip.type.${css.source_type}`) || css.source_type);
            }
        }
    }

    if (isRunning) {
        const fpsCurrent = state.fps_current ?? 0;
        const fpsActual = state.fps_actual != null ? state.fps_actual.toFixed(1) : '-';
        const fpsTarget = state.fps_target || (target.settings || target.key_colors_settings || {}).fps || '-';
        const uptime = formatUptime(metrics.uptime_seconds);
        const errors = metrics.errors_count || 0;

        // Set uptime base for interpolation
        if (metrics.uptime_seconds != null) {
            _setUptimeBase(target.id, metrics.uptime_seconds);
        }

        // Push FPS samples to history
        if (state.fps_actual != null) {
            _pushFps(target.id, state.fps_actual, fpsCurrent);
        }

        let healthDot = '';
        if (isLed && state.device_last_checked != null) {
            const cls = state.device_online ? 'health-online' : 'health-offline';
            healthDot = `<span class="health-dot ${cls}"></span>`;
        }

        const cStyle = cardColorStyle(target.id);
        return `<div class="dashboard-target dashboard-card-link" data-target-id="${target.id}" onclick="${navOnclick}"${cStyle ? ` style="${cStyle}"` : ''}>
            <div class="dashboard-target-info">
                <span class="dashboard-target-icon">${icon}</span>
                <div>
                    <div class="dashboard-target-name">${escapeHtml(target.name)}${healthDot}</div>
                    ${subtitleParts.length ? `<div class="dashboard-target-subtitle">${escapeHtml(subtitleParts.join(' · '))}</div>` : ''}
                </div>
            </div>
            <div class="dashboard-target-metrics">
                <div class="dashboard-metric dashboard-fps-metric">
                    <div class="dashboard-fps-sparkline">
                        <canvas id="dashboard-fps-${target.id}" data-fps-target="${fpsTarget}"></canvas>
                    </div>
                    <div class="dashboard-fps-label">
                        <span class="dashboard-metric-value" data-fps-text="${target.id}">${fpsCurrent}<span class="dashboard-fps-target">/${fpsTarget}</span><span class="dashboard-fps-avg">avg ${fpsActual}</span></span>
                    </div>
                </div>
                <div class="dashboard-metric" title="${t('dashboard.uptime')}">
                    <div class="dashboard-metric-value" data-uptime-text="${target.id}">${ICON_CLOCK} ${uptime}</div>
                </div>
                <div class="dashboard-metric" title="${t('dashboard.errors')}">
                    <div class="dashboard-metric-value" data-errors-text="${target.id}" title="${errors}">${errors > 0 ? ICON_WARNING : ICON_OK} ${formatCompact(errors)}</div>
                </div>
            </div>
            <div class="dashboard-target-actions">
                <button class="dashboard-action-btn stop" onclick="dashboardStopTarget('${target.id}')" title="${t('device.button.stop')}">${ICON_STOP_PLAIN}</button>
            </div>
        </div>`;
    } else {
        const cStyle2 = cardColorStyle(target.id);
        return `<div class="dashboard-target dashboard-card-link" onclick="${navOnclick}"${cStyle2 ? ` style="${cStyle2}"` : ''}>
            <div class="dashboard-target-info">
                <span class="dashboard-target-icon">${icon}</span>
                <div>
                    <div class="dashboard-target-name">${escapeHtml(target.name)}</div>
                    ${subtitleParts.length ? `<div class="dashboard-target-subtitle">${escapeHtml(subtitleParts.join(' · '))}</div>` : ''}
                </div>
            </div>
            <div class="dashboard-target-metrics"></div>
            <div class="dashboard-target-actions">
                <button class="dashboard-action-btn start" onclick="dashboardStartTarget('${target.id}')" title="${t('device.button.start')}">${ICON_START}</button>
            </div>
        </div>`;
    }
}

function renderDashboardAutomation(automation, sceneMap = new Map()) {
    const isActive = automation.is_active;
    const isDisabled = !automation.enabled;

    let condSummary = '';
    if (automation.conditions.length > 0) {
        const parts = automation.conditions.map(c => {
            if (c.condition_type === 'application') {
                const apps = (c.apps || []).join(', ');
                const matchLabel = c.match_type === 'topmost' ? t('automations.condition.application.match_type.topmost') : t('automations.condition.application.match_type.running');
                return `${apps} (${matchLabel})`;
            }
            if (c.condition_type === 'startup') return t('automations.condition.startup');
            if (c.condition_type === 'time_of_day') return t('automations.condition.time_of_day');
            return t(`automations.condition.${c.condition_type}`) || c.condition_type;
        });
        const logic = automation.condition_logic === 'and' ? ' & ' : ' | ';
        condSummary = parts.join(logic);
    }

    const statusBadge = isDisabled
        ? `<span class="dashboard-badge-stopped">${t('automations.status.disabled')}</span>`
        : isActive
            ? `<span class="dashboard-badge-active">${t('automations.status.active')}</span>`
            : `<span class="dashboard-badge-stopped">${t('automations.status.inactive')}</span>`;

    // Scene info
    const scene = automation.scene_preset_id ? sceneMap.get(automation.scene_preset_id) : null;
    const sceneName = scene ? escapeHtml(scene.name) : t('automations.scene.none_selected');

    const aStyle = cardColorStyle(automation.id);
    return `<div class="dashboard-target dashboard-automation dashboard-card-link" data-automation-id="${automation.id}" onclick="if(!event.target.closest('button')){navigateToCard('automations',null,'automations','data-automation-id','${automation.id}')}"${aStyle ? ` style="${aStyle}"` : ''}>
        <div class="dashboard-target-info">
            <span class="dashboard-target-icon">${ICON_AUTOMATION}</span>
            <div>
                <div class="dashboard-target-name">${escapeHtml(automation.name)}</div>
                ${condSummary ? `<div class="dashboard-target-subtitle">${escapeHtml(condSummary)}</div>` : ''}
                <div class="dashboard-target-subtitle">${ICON_SCENE} ${sceneName}</div>
            </div>
            ${statusBadge}
        </div>
        <div class="dashboard-target-actions">
            <button class="dashboard-action-btn ${automation.enabled ? 'stop' : 'start'}" onclick="dashboardToggleAutomation('${automation.id}', ${!automation.enabled})" title="${automation.enabled ? t('automations.action.disable') : t('automations.status.active')}">
                ${automation.enabled ? ICON_STOP_PLAIN : ICON_START}
            </button>
        </div>
    </div>`;
}

export async function dashboardToggleAutomation(automationId, enable) {
    try {
        const endpoint = enable ? 'enable' : 'disable';
        const response = await fetchWithAuth(`/automations/${automationId}/${endpoint}`, {
            method: 'POST',
        });
        if (response.ok) {
            loadDashboard();
        }
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('dashboard.error.automation_toggle_failed'), 'error');
    }
}

export async function dashboardStartTarget(targetId) {
    try {
        const response = await fetchWithAuth(`/output-targets/${targetId}/start`, {
            method: 'POST',
        });
        if (response.ok) {
            showToast(t('device.started'), 'success');
            loadDashboard();
        } else {
            const error = await response.json();
            showToast(error.detail || t('dashboard.error.start_failed'), 'error');
        }
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('dashboard.error.start_failed'), 'error');
    }
}

export async function dashboardStopTarget(targetId) {
    try {
        const response = await fetchWithAuth(`/output-targets/${targetId}/stop`, {
            method: 'POST',
        });
        if (response.ok) {
            showToast(t('device.stopped'), 'success');
            loadDashboard();
        } else {
            const error = await response.json();
            showToast(error.detail || t('dashboard.error.stop_failed'), 'error');
        }
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('dashboard.error.stop_failed'), 'error');
    }
}

export async function dashboardStopAll() {
    const confirmed = await showConfirm(t('confirm.stop_all'));
    if (!confirmed) return;
    try {
        const [allTargets, statesResp] = await Promise.all([
            outputTargetsCache.fetch().catch(() => []),
            fetchWithAuth('/output-targets/batch/states'),
        ]);
        const statesData = statesResp.ok ? await statesResp.json() : { states: {} };
        const states = statesData.states || {};
        const running = allTargets.filter(t => states[t.id]?.processing);
        await Promise.all(running.map(t =>
            fetchWithAuth(`/output-targets/${t.id}/stop`, { method: 'POST' }).catch(() => {})
        ));
        loadDashboard();
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('dashboard.error.stop_all'), 'error');
    }
}

export async function dashboardPauseClock(clockId) {
    try {
        const resp = await fetchWithAuth(`/sync-clocks/${clockId}/pause`, { method: 'POST' });
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        showToast(t('sync_clock.paused'), 'success');
        loadDashboard(true);
    } catch (e) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

export async function dashboardResumeClock(clockId) {
    try {
        const resp = await fetchWithAuth(`/sync-clocks/${clockId}/resume`, { method: 'POST' });
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        showToast(t('sync_clock.resumed'), 'success');
        loadDashboard(true);
    } catch (e) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

export async function dashboardResetClock(clockId) {
    try {
        const resp = await fetchWithAuth(`/sync-clocks/${clockId}/reset`, { method: 'POST' });
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        showToast(t('sync_clock.reset_done'), 'success');
        loadDashboard(true);
    } catch (e) {
        if (e.isAuth) return;
        showToast(e.message, 'error');
    }
}

export function stopUptimeTimer() {
    _stopUptimeTimer();
}

// React to global server events when dashboard tab is active
function _isDashboardActive() {
    return (localStorage.getItem('activeTab') || 'dashboard') === 'dashboard';
}

let _eventDebounceTimer = null;
function _debouncedDashboardReload(forceFullRender = false) {
    if (!_isDashboardActive()) return;
    clearTimeout(_eventDebounceTimer);
    _eventDebounceTimer = setTimeout(() => loadDashboard(forceFullRender), 300);
}

document.addEventListener('server:state_change', () => _debouncedDashboardReload());
document.addEventListener('server:automation_state_changed', () => _debouncedDashboardReload(true));
document.addEventListener('server:device_health_changed', () => _debouncedDashboardReload());

const _DASHBOARD_ENTITY_TYPES = new Set(['output_target', 'automation', 'scene_preset', 'sync_clock', 'device']);
document.addEventListener('server:entity_changed', (e) => {
    const { entity_type } = e.detail || {};
    if (_DASHBOARD_ENTITY_TYPES.has(entity_type)) _debouncedDashboardReload(true);
});

// Re-render dashboard when language changes
document.addEventListener('languageChanged', () => {
    if (!apiKey) return;
    // Force perf section rebuild with new locale
    const perfEl = document.querySelector('.dashboard-perf-persistent');
    if (perfEl) perfEl.remove();
    loadDashboard();
});

// Pause uptime timer when browser tab is hidden, resume when visible
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        _stopUptimeTimer();
    } else if (_isDashboardActive() && _lastRunningIds.length > 0) {
        _cacheUptimeElements();
        _startUptimeTimer();
    }
});
