import http from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { WebSocketServer } from 'ws';
import sqlite3 from 'sqlite3';
import { getRulesStatus, setRuleConfig } from './rule_engine.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const PORT = 8081;
const MODEL_PICS_DIR = path.join(__dirname, 'modelPics');

// SQLite database connection (read-only for status queries)
const db = new sqlite3.Database('devices.db', sqlite3.OPEN_READONLY);

// WebSocket clients for broadcasting
const wsClients = new Set();

// Query initial status data
function getStatusData() {
    return new Promise((resolve, reject) => {
        const sql = `
            SELECT d.mac, d.model, d.connected, d.last_seen,
                   c.id as channel_id, c.component, c.field, c.type,
                   (SELECT e.event FROM events e WHERE e.channel_id = c.id ORDER BY e.id DESC LIMIT 1) as last_event,
                   (SELECT e.timestamp FROM events e WHERE e.channel_id = c.id ORDER BY e.id DESC LIMIT 1) as last_ts
            FROM devices d
            LEFT JOIN channels c ON c.mac = d.mac
            ORDER BY d.mac, c.component, c.field
        `;
        db.all(sql, [], (err, rows) => {
            if (err) reject(err);
            else {
                // Group by device
                const devices = {};
                for (const row of rows) {
                    if (!devices[row.mac]) {
                        devices[row.mac] = {
                            mac: row.mac,
                            model: row.model,
                            connected: row.connected,
                            last_seen: row.last_seen,
                            channels: []
                        };
                    }
                    if (row.channel_id) {
                        devices[row.mac].channels.push({
                            id: row.channel_id,
                            component: row.component,
                            field: row.field,
                            type: row.type,
                            event: row.last_event,
                            timestamp: row.last_ts
                        });
                    }
                }
                resolve(Object.values(devices));
            }
        });
    });
}

// Query channel history with time range filter
function getChannelHistory(channelId, range = '24h') {
    return new Promise((resolve, reject) => {
        // Calculate the start timestamp based on range
        const now = new Date();
        let startTime;
        if (range === '1week') {
            startTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
        } else {
            // Default to 24h
            startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
        }

        const sql = `
            SELECT c.component, c.field, c.type, c.mac,
                   e.event, e.timestamp
            FROM channels c
            LEFT JOIN events e ON e.channel_id = c.id
            WHERE c.id = ? AND (e.timestamp IS NULL OR e.timestamp >= ?)
            ORDER BY e.timestamp ASC
        `;

        db.all(sql, [channelId, startTime.toISOString()], (err, rows) => {
            if (err) reject(err);
            else if (rows.length === 0) {
                reject(new Error('Channel not found'));
            } else {
                const channel = {
                    id: channelId,
                    component: rows[0].component,
                    field: rows[0].field,
                    type: rows[0].type,
                    mac: rows[0].mac
                };
                const events = rows
                    .filter(r => r.timestamp !== null)
                    .map(r => ({
                        event: r.event,
                        timestamp: r.timestamp
                    }));
                resolve({ channel, events });
            }
        });
    });
}

// Broadcast event to all connected WebSocket clients
export function broadcastEvent(mac, component, field, type, event) {
    const message = JSON.stringify({
        type: 'event',
        mac,
        component,
        field,
        eventType: type,
        event,
        timestamp: new Date().toISOString()
    });
    for (const client of wsClients) {
        if (client.readyState === 1) { // OPEN
            client.send(message);
        }
    }
}

// Broadcast rule status update to all connected WebSocket clients
export function broadcastRuleUpdate(ruleName, status) {
    const message = JSON.stringify({
        type: 'rule_update',
        name: ruleName,
        status,
        timestamp: new Date().toISOString()
    });
    for (const client of wsClients) {
        if (client.readyState === 1) { // OPEN
            client.send(message);
        }
    }
}

// HTML Dashboard
const dashboardHTML = `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IoT Status</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
    <style>
        :root {
            --bg-primary: #0f0f14;
            --bg-secondary: #1a1a24;
            --bg-card: rgba(30, 30, 45, 0.8);
            --border-color: rgba(255, 255, 255, 0.1);
            --text-primary: #ffffff;
            --text-secondary: #a0a0b0;
            --accent-online: #22c55e;
            --accent-offline: #ef4444;
            --accent-blue: #3b82f6;
        }
        
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            background: var(--bg-primary);
            color: var(--text-primary);
            min-height: 100vh;
            padding: 2rem;
        }
        
        .header {
            text-align: center;
            margin-bottom: 2rem;
        }
        
        .header h1 {
            font-size: 2rem;
            font-weight: 700;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
        }
        
        .header p {
            color: var(--text-secondary);
            margin-top: 0.5rem;
        }
        
        .status-indicator {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            font-size: 0.875rem;
        }
        
        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            animation: pulse 2s infinite;
        }
        
        .status-dot.connected {
            background: var(--accent-online);
            box-shadow: 0 0 10px var(--accent-online);
        }
        
        #connection-status.connected {
            color: var(--accent-online);
        }
        
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }
        
        .devices-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
            gap: 1.5rem;
            max-width: 1400px;
            margin: 0 auto;
        }
        
        .device-card {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: 16px;
            padding: 1.5rem;
            backdrop-filter: blur(10px);
            transition: transform 0.2s, box-shadow 0.2s;
        }
        
        .device-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
        }
        
        .device-card.flash {
            animation: flash-update 0.5s ease-out;
        }
        
        @keyframes flash-update {
            0% { box-shadow: 0 0 30px rgba(59, 130, 246, 0.8); }
            100% { box-shadow: none; }
        }
        
        .device-header {
            display: flex;
            align-items: center;
            gap: 1rem;
            margin-bottom: 1rem;
            padding-bottom: 1rem;
            border-bottom: 1px solid var(--border-color);
        }
        
        .device-image {
            width: 64px;
            height: 64px;
            object-fit: contain;
            border-radius: 8px;
            background: var(--bg-secondary);
            padding: 8px;
        }
        
        .device-info {
            flex: 1;
        }
        
        .device-mac {
            font-family: monospace;
            font-size: 0.9rem;
            font-weight: 600;
        }
        
        .device-model {
            font-size: 0.75rem;
            color: var(--text-secondary);
            margin-top: 2px;
        }
        
        .device-status {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            padding: 0.375rem 0.75rem;
            border-radius: 999px;
            font-size: 0.75rem;
            font-weight: 600;
            text-transform: uppercase;
        }
        
        .device-status.online {
            background: rgba(34, 197, 94, 0.2);
            color: var(--accent-online);
        }
        
        .device-status.offline {
            background: rgba(239, 68, 68, 0.2);
            color: var(--accent-offline);
        }
        
        .device-status .dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: currentColor;
        }
        
        .channels-table {
            width: 100%;
            font-size: 0.8rem;
        }
        
        .channels-table th {
            text-align: left;
            color: var(--text-secondary);
            font-weight: 500;
            padding: 0.5rem 0;
            border-bottom: 1px solid var(--border-color);
        }
        
        .channels-table td {
            padding: 0.5rem 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.05);
        }
        
        .channels-table tr:last-child td {
            border-bottom: none;
        }
        
        .channel-component {
            font-family: monospace;
            color: var(--accent-blue);
        }
        
        .channel-field {
            color: var(--text-secondary);
        }
        
        .channel-event {
            font-family: monospace;
            font-weight: 500;
        }
        
        .channel-timestamp {
            font-size: 0.7rem;
            color: var(--text-secondary);
        }
        
        .channel-event.flash {
            animation: value-flash 0.5s ease-out;
        }
        
        @keyframes value-flash {
            0% { color: #fbbf24; transform: scale(1.1); }
            100% { color: inherit; transform: scale(1); }
        }
        
        .empty-state {
            text-align: center;
            padding: 4rem 2rem;
            color: var(--text-secondary);
        }
        
        .empty-state svg {
            width: 64px;
            height: 64px;
            margin-bottom: 1rem;
            opacity: 0.5;
        }
        
        /* Toast notifications */
        .toast-container {
            position: fixed;
            bottom: 1.5rem;
            right: 1.5rem;
            display: flex;
            flex-direction: column-reverse;
            gap: 0.5rem;
            z-index: 1000;
            max-height: 80vh;
            overflow: hidden;
        }
        
        .toast {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: 12px;
            padding: 0.875rem 1rem;
            backdrop-filter: blur(10px);
            min-width: 280px;
            max-width: 360px;
            animation: toast-in 0.3s ease-out;
            display: flex;
            align-items: center;
            gap: 0.75rem;
        }
        
        .toast.removing {
            animation: toast-out 0.3s ease-in forwards;
        }
        
        @keyframes toast-in {
            from { opacity: 0; transform: translateX(100px); }
            to { opacity: 1; transform: translateX(0); }
        }
        
        @keyframes toast-out {
            from { opacity: 1; transform: translateX(0); }
            to { opacity: 0; transform: translateX(100px); }
        }
        
        .toast-icon {
            width: 32px;
            height: 32px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
        }
        
        .toast-icon.online {
            background: rgba(34, 197, 94, 0.2);
            color: var(--accent-online);
        }
        
        .toast-icon.offline {
            background: rgba(239, 68, 68, 0.2);
            color: var(--accent-offline);
        }
        
        .toast-icon.event {
            background: rgba(59, 130, 246, 0.2);
            color: var(--accent-blue);
        }
        
        .toast-content {
            flex: 1;
            min-width: 0;
        }
        
        .toast-title {
            font-weight: 600;
            font-size: 0.8rem;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        
        .toast-message {
            font-size: 0.75rem;
            color: var(--text-secondary);
            margin-top: 2px;
        }
        
        /* Rules section */
        .section-header {
            max-width: 1400px;
            margin: 2rem auto 1rem;
            font-size: 1.25rem;
            font-weight: 600;
            color: var(--text-secondary);
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }
        
        .section-header svg {
            width: 20px;
            height: 20px;
        }
        
        .rules-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 1rem;
            max-width: 1400px;
            margin: 0 auto 2rem;
        }
        
        .rule-card {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: 12px;
            padding: 1rem 1.25rem;
            backdrop-filter: blur(10px);
        }
        
        .rule-name {
            font-family: monospace;
            font-size: 0.85rem;
            font-weight: 600;
            margin-bottom: 0.75rem;
            color: var(--accent-blue);
        }
        
        .rule-status {
            display: flex;
            flex-wrap: wrap;
            gap: 0.5rem;
        }
        
        .rule-stat {
            background: var(--bg-secondary);
            padding: 0.375rem 0.625rem;
            border-radius: 6px;
            font-size: 0.75rem;
        }
        
        .rule-stat-label {
            color: var(--text-secondary);
            margin-right: 0.25rem;
        }
        
        .rule-stat-value {
            font-family: monospace;
            font-weight: 500;
        }
        
        /* Clickable channel rows */
        .channels-table tbody tr {
            cursor: pointer;
            transition: background-color 0.15s ease;
        }
        
        .channels-table tbody tr:hover {
            background: rgba(59, 130, 246, 0.1);
        }
        
        /* Modal overlay */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.8);
            backdrop-filter: blur(4px);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 2000;
            opacity: 0;
            visibility: hidden;
            transition: all 0.2s ease;
        }
        
        .modal-overlay.active {
            opacity: 1;
            visibility: visible;
        }
        
        .modal-content {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: 16px;
            padding: 1.5rem;
            width: 90%;
            max-width: 900px;
            max-height: 80vh;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            backdrop-filter: blur(10px);
            transform: scale(0.95);
            transition: transform 0.2s ease;
        }
        
        .modal-overlay.active .modal-content {
            transform: scale(1);
        }
        
        .modal-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 1rem;
            padding-bottom: 1rem;
            border-bottom: 1px solid var(--border-color);
        }
        
        .modal-title {
            font-size: 1.1rem;
            font-weight: 600;
        }
        
        .modal-subtitle {
            font-size: 0.8rem;
            color: var(--text-secondary);
            margin-top: 0.25rem;
        }
        
        .modal-close {
            background: none;
            border: none;
            color: var(--text-secondary);
            cursor: pointer;
            padding: 0.5rem;
            border-radius: 8px;
            transition: all 0.15s ease;
        }
        
        .modal-close:hover {
            background: rgba(255, 255, 255, 0.1);
            color: var(--text-primary);
        }
        
        .range-toggle {
            display: flex;
            gap: 0.5rem;
            margin-bottom: 1rem;
        }
        
        .range-btn {
            padding: 0.5rem 1rem;
            background: var(--bg-secondary);
            border: 1px solid var(--border-color);
            border-radius: 8px;
            color: var(--text-secondary);
            cursor: pointer;
            font-family: inherit;
            font-size: 0.8rem;
            transition: all 0.15s ease;
        }
        
        .range-btn:hover {
            background: rgba(59, 130, 246, 0.1);
            border-color: var(--accent-blue);
        }
        
        .range-btn.active {
            background: var(--accent-blue);
            border-color: var(--accent-blue);
            color: white;
        }
        
        .chart-container {
            flex: 1;
            min-height: 300px;
            position: relative;
        }
        
        .chart-loading {
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            color: var(--text-secondary);
        }
        
        .chart-empty {
            text-align: center;
            color: var(--text-secondary);
            padding: 2rem;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>IoT Status</h1>
        <p class="status-indicator">
            <span class="status-dot" id="ws-dot"></span>
            <span id="connection-status">Connecting...</span>
        </p>
    </div>
    
    <div id="devices-container" class="devices-grid">
        <div class="empty-state">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2z" />
            </svg>
            <p>Loading devices...</p>
        </div>
    </div>
    
    <div class="section-header">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
        </svg>
        Rules
    </div>
    <div id="rules-container" class="rules-grid"></div>
    
    <!-- Chart Modal -->
    <div id="chart-modal" class="modal-overlay">
        <div class="modal-content">
            <div class="modal-header">
                <div>
                    <div class="modal-title" id="modal-title">Channel History</div>
                    <div class="modal-subtitle" id="modal-subtitle"></div>
                </div>
                <button class="modal-close" onclick="closeChartModal()">
                    <svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>
            <div class="range-toggle">
                <button class="range-btn active" data-range="24h" onclick="setChartRange('24h')">24 Hours</button>
                <button class="range-btn" data-range="1week" onclick="setChartRange('1week')">1 Week</button>
            </div>
            <div class="chart-container">
                <canvas id="history-chart"></canvas>
                <div class="chart-loading" id="chart-loading">Loading...</div>
            </div>
        </div>
    </div>
    
    <div id="toast-container" class="toast-container"></div>

    <script>
        let devices = {};
        let ws;
        let retryCount = 0;
        let connectionTimeout;
        
        function connectWebSocket() {
            retryCount++;
            console.log('WebSocket: Connecting... (attempt ' + retryCount + ')');
            document.getElementById('connection-status').textContent = 'Connecting... (attempt ' + retryCount + ')';
            
            // Clear any existing connection
            if (ws) {
                ws.onclose = null;
                ws.onerror = null;
                ws.onopen = null;
                try { ws.close(); } catch(e) {}
            }
            
            try {
                ws = new WebSocket('ws://' + window.location.host);
            } catch (err) {
                console.error('WebSocket: Failed to create', err);
                setTimeout(connectWebSocket, 2000);
                return;
            }
            
            // Connection timeout - abort if no response in 3 seconds
            connectionTimeout = setTimeout(() => {
                console.log('WebSocket: Connection timeout, retrying...');
                if (ws.readyState === WebSocket.CONNECTING) {
                    ws.close();
                }
            }, 3000);
            
            ws.onopen = () => {
                clearTimeout(connectionTimeout);
                console.log('WebSocket: Connected');
                retryCount = 0;
                document.getElementById('ws-dot').classList.add('connected');
                document.getElementById('connection-status').textContent = 'Connected';
                document.getElementById('connection-status').classList.add('connected');
            };
            
            ws.onclose = (e) => {
                clearTimeout(connectionTimeout);
                console.log('WebSocket: Closed', e.code, e.reason);
                document.getElementById('ws-dot').classList.remove('connected');
                document.getElementById('connection-status').textContent = 'Disconnected - Reconnecting...';
                document.getElementById('connection-status').classList.remove('connected');
                setTimeout(connectWebSocket, 2000);
            };
            
            ws.onerror = (e) => {
                console.error('WebSocket: Error', e);
            };
            
            ws.onmessage = (event) => {
                const data = JSON.parse(event.data);
                
                if (data.type === 'init') {
                    // Initial data load
                    devices = {};
                    for (const device of data.devices) {
                        devices[device.mac] = device;
                    }
                    renderDevices();
                    if (data.rules) {
                        renderRules(data.rules);
                    }
                } else if (data.type === 'event') {
                    // Real-time event update
                    handleEvent(data);
                } else if (data.type === 'rule_update') {
                    // Real-time rule status update
                    handleRuleUpdate(data);
                } else if (data.type === 'rules_update') {
                    // Full rules refresh (after config change)
                    renderRules(data.rules);
                }
            };
        }
        
        let currentRules = [];
        
        function sendConfig(ruleName, key, value) {
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({
                    type: 'set_config',
                    ruleName,
                    key,
                    value
                }));
                showToast(ruleName, 'Saving...', key + ' = ' + value, 'event');
            }
        }
        
        function handleRuleUpdate(data) {
            // Update the rule in our local cache
            const ruleIndex = currentRules.findIndex(r => r.name === data.name);
            if (ruleIndex >= 0) {
                currentRules[ruleIndex].status = data.status;
            } else {
                currentRules.push({ name: data.name, status: data.status });
            }
            
            // Re-render rules
            renderRules(currentRules);
            
            // Show toast for the update
            const action = data.status.lastAction || 'Status updated';
            showToast(data.name, action, data.name, 'event');
        }
        
        function handleEvent(data) {
            const { mac, component, field, event, eventType } = data;
            
            if (!devices[mac]) {
                // New device, request full refresh
                console.log('New device detected (' + mac + '), requesting refresh...');
                if (ws && ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify({ type: 'get_status' }));
                }
                return;
            }
            
            // Flash the device card
            const card = document.querySelector(\`[data-mac="\${mac}"]\`);
            if (card) {
                card.classList.remove('flash');
                void card.offsetWidth; // Trigger reflow
                card.classList.add('flash');
            }
            
            // Update channel value
            const device = devices[mac];
            let channel = device.channels.find(c => c.component === component && c.field === field);
            
            if (channel) {
                channel.event = event;
                channel.timestamp = data.timestamp;
            } else {
                device.channels.push({
                    component,
                    field,
                    type: eventType,
                    event,
                    timestamp: data.timestamp
                });
            }
            
            // Update online status if it's a system.online event
            if (component === 'system' && field === 'online') {
                device.connected = event === 'true' || event === true ? 1 : 0;
                const isOnline = device.connected === 1;
                showToast(
                    device.mac,
                    isOnline ? \`Device Online\` : \`Device Offline\`,
                    \`\${device.model}\`,
                    isOnline ? 'online' : 'offline'
                );
            } else {
                // Suppress high-frequency power and RSSI events from notifications
                if ((component.startsWith('power') && (field === 'apower' || field === 'aenergy')) ||
                    (component === 'wifi' && field === 'rssi') ||
                    (component === 'rf' && field === 'rssi')) {
                    // Do not show toast
                } else {
                    // Show toast for other events
                    showToast(
                        device.mac,
                        \`\${component}.\${field}\`,
                        \`\${event}\`,
                        'event'
                    );
                }
            }
            
            // Re-render the specific device card
            renderDevice(device);
            
            // Flash the updated value
            const eventCell = document.querySelector(\`[data-mac="\${mac}"] [data-channel="\${component}_\${field}"]\`);
            if (eventCell) {
                eventCell.classList.remove('flash');
                void eventCell.offsetWidth;
                eventCell.classList.add('flash');
            }
        }
        
        function showToast(mac, title, message, type) {
            const container = document.getElementById('toast-container');
            const toast = document.createElement('div');
            toast.className = 'toast';
            
            const iconSvg = type === 'online' 
                ? '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg>'
                : type === 'offline'
                ? '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>'
                : '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>';
            
            toast.innerHTML = \`
                <div class="toast-icon \${type}">\${iconSvg}</div>
                <div class="toast-content">
                    <div class="toast-title">\${title}</div>
                    <div class="toast-message">\${message}</div>
                </div>
            \`;
            
            container.appendChild(toast);
            
            // Auto-remove after 4 seconds
            setTimeout(() => {
                toast.classList.add('removing');
                setTimeout(() => toast.remove(), 300);
            }, 4000);
            
            // Limit to 5 toasts max
            while (container.children.length > 5) {
                container.firstChild.remove();
            }
        }
        
        function renderDevices() {
            const container = document.getElementById('devices-container');
            
            if (Object.keys(devices).length === 0) {
                container.innerHTML = \`
                    <div class="empty-state">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2z" />
                        </svg>
                        <p>No devices found</p>
                    </div>
                \`;
                return;
            }
            
            container.innerHTML = Object.values(devices).map(d => renderDeviceHTML(d)).join('');
        }
        
        function renderDevice(device) {
            const existing = document.querySelector(\`[data-mac="\${device.mac}"]\`);
            if (existing) {
                const temp = document.createElement('div');
                temp.innerHTML = renderDeviceHTML(device);
                existing.outerHTML = temp.innerHTML;
            }
        }
        
        function renderDeviceHTML(device) {
            const isOnline = device.connected === 1;
            const statusClass = isOnline ? 'online' : 'offline';
            const statusText = isOnline ? 'Online' : 'Offline';
            
            const channelsHTML = device.channels.length > 0 ? \`
                <table class="channels-table">
                    <thead>
                        <tr>
                            <th>Component</th>
                            <th>Field</th>
                            <th>Value</th>
                            <th>When</th>
                        </tr>
                    </thead>
                    <tbody>
                        \${device.channels.map(ch => \`
                            <tr data-channel-id="\${ch.id}" onclick="openChartModal(\${ch.id}, '\${device.mac}', '\${ch.component}', '\${ch.field}')">
                                <td class="channel-component">\${ch.component}</td>
                                <td class="channel-field">\${ch.field}</td>
                                <td class="channel-event" data-channel="\${ch.component}_\${ch.field}">\${ch.event ?? '-'}</td>
                                <td class="channel-timestamp">\${formatTimestamp(ch.timestamp)}</td>
                            </tr>
                        \`).join('')}
                    </tbody>
                </table>
            \` : '<p style="color: var(--text-secondary); font-size: 0.8rem;">No channels</p>';
            
            return \`
                <div class="device-card" data-mac="\${device.mac}">
                    <div class="device-header">
                        <img class="device-image" src="/modelPics/\${device.model}.png" 
                             onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22%23666%22><rect width=%2224%22 height=%2224%22 rx=%224%22/><text x=%2212%22 y=%2216%22 text-anchor=%22middle%22 fill=%22%23999%22 font-size=%228%22>?</text></svg>'"
                             alt="\${device.model}">
                        <div class="device-info">
                            <div class="device-mac">\${device.mac}</div>
                            <div class="device-model">\${device.model}</div>
                        </div>
                        <div class="device-status \${statusClass}">
                            <span class="dot"></span>
                            \${statusText}
                        </div>
                    </div>
                    \${channelsHTML}
                </div>
            \`;
        }
        
        function formatTimestamp(ts) {
            if (!ts) return '-';
            const date = new Date(ts);
            const now = new Date();
            const diffMs = now - date;
            const diffMins = Math.floor(diffMs / 60000);
            const diffHours = Math.floor(diffMs / 3600000);
            const diffDays = Math.floor(diffMs / 86400000);
            
            if (diffMins < 1) return 'just now';
            if (diffMins < 60) return \`\${diffMins}m ago\`;
            if (diffHours < 24) return \`\${diffHours}h ago\`;
            if (diffDays < 7) return \`\${diffDays}d ago\`;
            return date.toLocaleDateString();
        }
        
        function formatDuration(ms) {
            if (ms < 1000) return \`\${ms}ms\`;
            const secs = Math.floor(ms / 1000);
            if (secs < 60) return \`\${secs}s\`;
            const mins = Math.floor(secs / 60);
            const remainingSecs = secs % 60;
            return \`\${mins}m \${remainingSecs}s\`;
        }
        
        function renderRules(rules) {
            const container = document.getElementById('rules-container');
            currentRules = rules; // Store for updates
            
            if (!rules || rules.length === 0) {
                container.innerHTML = '<p style="color: var(--text-secondary); font-size: 0.8rem;">No rules loaded</p>';
                return;
            }
            
            container.innerHTML = rules.map(rule => {
                let statusHTML = '';
                if (rule.status === null) {
                    statusHTML = '<span style="color: var(--text-secondary); font-size: 0.75rem;">No status hook</span>';
                } else if (rule.status.error) {
                    statusHTML = \`<span style="color: var(--accent-offline);">Error: \${rule.status.error}</span>\`;
                } else {
                    // Display each status property as a badge or input
                    const stats = Object.entries(rule.status).map(([key, value]) => {
                        let displayValue = value;
                        let isEditable = key === 'storedDuration';
                        
                        if (key.toLowerCase().includes('duration') && typeof value === 'number') {
                            const secs = Math.round(value / 1000);
                            if (isEditable) {
                                return \`
                                    <div class="rule-stat editable">
                                        <span class="rule-stat-label">\${key}:</span>
                                        <input type="number" class="rule-input" value="\${secs}" min="1" max="300" 
                                               onchange="sendConfig('\${rule.name}', '\${key}', this.value * 1000)"
                                               style="width: 50px; background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 2px 4px; font-family: monospace;">
                                        <span style="color: var(--text-secondary);">s</span>
                                    </div>
                                \`;
                            }
                            displayValue = formatDuration(value);
                        } else if (typeof value === 'boolean') {
                            displayValue = value ? '✓' : '✗';
                        }
                        return \`
                            <div class="rule-stat">
                                <span class="rule-stat-label">\${key}:</span>
                                <span class="rule-stat-value">\${displayValue}</span>
                            </div>
                        \`;
                    }).join('');
                    statusHTML = \`<div class="rule-status">\${stats}</div>\`;
                }
                
                return \`
                    <div class="rule-card">
                        <div class="rule-name">\${rule.name}</div>
                        \${statusHTML}
                    </div>
                \`;
            }).join('');
        }
        
        // Chart modal functions
        let historyChart = null;
        let currentChannelId = null;
        let currentRange = '24h';
        
        function openChartModal(channelId, mac, component, field) {
            currentChannelId = channelId;
            currentRange = '24h';
            
            // Update modal content
            document.getElementById('modal-title').textContent = \`\${component}.\${field}\`;
            document.getElementById('modal-subtitle').textContent = mac;
            
            // Reset range buttons
            document.querySelectorAll('.range-btn').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.range === '24h');
            });
            
            // Show modal
            document.getElementById('chart-modal').classList.add('active');
            
            // Load chart data
            loadChartData(channelId, '24h');
        }
        
        function closeChartModal() {
            document.getElementById('chart-modal').classList.remove('active');
            if (historyChart) {
                historyChart.destroy();
                historyChart = null;
            }
        }
        
        function setChartRange(range) {
            if (range === currentRange) return;
            currentRange = range;
            
            // Update button states
            document.querySelectorAll('.range-btn').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.range === range);
            });
            
            // Reload data
            loadChartData(currentChannelId, range);
        }
        
        async function loadChartData(channelId, range) {
            const loadingEl = document.getElementById('chart-loading');
            loadingEl.style.display = 'flex';
            loadingEl.textContent = 'Loading...';
            
            try {
                const res = await fetch(\`/api/channel-history/\${channelId}?range=\${range}\`);
                if (!res.ok) throw new Error('Failed to load data');
                
                const data = await res.json();
                
                if (data.events.length === 0) {
                    loadingEl.textContent = 'No data for this time range';
                    if (historyChart) {
                        historyChart.destroy();
                        historyChart = null;
                    }
                    return;
                }
                
                loadingEl.style.display = 'none';
                renderChart(data.events, data.channel.type);
                
            } catch (err) {
                console.error('Error loading chart data:', err);
                loadingEl.textContent = 'Error loading data';
            }
        }
        
        function renderChart(events, channelType) {
            const ctx = document.getElementById('history-chart').getContext('2d');
            
            // Destroy existing chart
            if (historyChart) {
                historyChart.destroy();
            }
            
            // Process data based on type
            const isBoolean = channelType === 'boolean';
            const data = events.map(e => ({
                x: new Date(e.timestamp),
                y: isBoolean ? (e.event === 'true' || e.event === true ? 1 : 0) : parseFloat(e.event)
            }));
            
            // Chart config
            const config = {
                type: 'line',
                data: {
                    datasets: [{
                        data: data,
                        borderColor: '#3b82f6',
                        backgroundColor: 'rgba(59, 130, 246, 0.1)',
                        fill: true,
                        tension: isBoolean ? 0 : 0.3,
                        stepped: isBoolean ? 'before' : false,
                        pointRadius: data.length > 100 ? 0 : 3,
                        pointHoverRadius: 5,
                        borderWidth: 2
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            callbacks: {
                                label: (context) => {
                                    if (isBoolean) {
                                        return context.raw.y === 1 ? 'true' : 'false';
                                    }
                                    return context.raw.y.toString();
                                }
                            }
                        }
                    },
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: currentRange === '24h' ? 'hour' : 'day',
                                displayFormats: {
                                    hour: 'HH:mm',
                                    day: 'MMM dd'
                                }
                            },
                            grid: {
                                color: 'rgba(255, 255, 255, 0.05)'
                            },
                            ticks: {
                                color: '#a0a0b0'
                            }
                        },
                        y: {
                            beginAtZero: isBoolean,
                            max: isBoolean ? 1.1 : undefined,
                            grid: {
                                color: 'rgba(255, 255, 255, 0.05)'
                            },
                            ticks: {
                                color: '#a0a0b0',
                                callback: isBoolean ? (val) => val === 1 ? 'true' : val === 0 ? 'false' : '' : undefined
                            }
                        }
                    },
                    interaction: {
                        intersect: false,
                        mode: 'index'
                    }
                }
            };
            
            historyChart = new Chart(ctx, config);
        }
        
        // Close modal on overlay click or Escape key
        document.getElementById('chart-modal').addEventListener('click', (e) => {
            if (e.target.id === 'chart-modal') closeChartModal();
        });
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') closeChartModal();
        });
        
        connectWebSocket();
    </script>
</body>
</html>`;

// Create HTTP server
const server = http.createServer(async (req, res) => {
    const url = new URL(req.url, `http://${req.headers.host}`);

    // Serve model images
    if (url.pathname.startsWith('/modelPics/')) {
        const filename = path.basename(url.pathname);
        const filepath = path.join(MODEL_PICS_DIR, filename);

        if (fs.existsSync(filepath)) {
            res.writeHead(200, { 'Content-Type': 'image/png' });
            fs.createReadStream(filepath).pipe(res);
        } else {
            res.writeHead(404);
            res.end('Not found');
        }
        return;
    }

    // API endpoint for channel history
    const historyMatch = url.pathname.match(/^\/api\/channel-history\/(\d+)$/);
    if (historyMatch) {
        const channelId = parseInt(historyMatch[1], 10);
        const range = url.searchParams.get('range') || '24h';

        try {
            const data = await getChannelHistory(channelId, range);
            res.writeHead(200, {
                'Content-Type': 'application/json',
                'Cache-Control': 'no-cache'
            });
            res.end(JSON.stringify(data));
        } catch (err) {
            console.error('[Status] Error fetching channel history:', err);
            res.writeHead(err.message === 'Channel not found' ? 404 : 500);
            res.end(JSON.stringify({ error: err.message }));
        }
        return;
    }

    // Serve dashboard
    if (url.pathname === '/' || url.pathname === '/index.html') {
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        });
        res.end(dashboardHTML);
        return;
    }

    res.writeHead(404);
    res.end('Not found');
});

// Create WebSocket server attached to HTTP server
const wss = new WebSocketServer({ server });

wss.on('connection', async (ws) => {
    console.log('[Status] Browser client connected');
    wsClients.add(ws);

    // Send initial status data
    try {
        const devices = await getStatusData();
        const rules = await getRulesStatus();
        ws.send(JSON.stringify({ type: 'init', devices, rules }));
    } catch (err) {
        console.error('[Status] Error fetching initial data:', err);
    }

    ws.on('message', async (message) => {
        try {
            const data = JSON.parse(message);
            if (data.type === 'set_config') {
                const { ruleName, key, value } = data;
                const success = setRuleConfig(ruleName, key, value);
                if (success) {
                    // Broadcast updated status to all clients
                    const rules = await getRulesStatus();
                    const updateMsg = JSON.stringify({ type: 'rules_update', rules });
                    for (const client of wsClients) {
                        if (client.readyState === 1) {
                            client.send(updateMsg);
                        }
                    }
                }
                ws.send(JSON.stringify({ type: 'set_config_result', success }));
            } else if (data.type === 'get_status') {
                // Client requested full status refresh
                const devices = await getStatusData();
                const rules = await getRulesStatus();
                ws.send(JSON.stringify({ type: 'init', devices, rules }));
            }
        } catch (err) {
            console.error('[Status] Error processing message:', err);
        }
    });

    ws.on('close', () => {
        console.log('[Status] Browser client disconnected');
        wsClients.delete(ws);
    });
});

// Start server function
export function startStatusServer() {
    server.listen(PORT, () => {
        console.log(`[Status] Dashboard server running at http://localhost:${PORT}`);
    });
}

// Allow running standalone
if (process.argv[1] === fileURLToPath(import.meta.url)) {
    startStatusServer();
}
