#!/usr/bin/env node
/**
 * DreamStack Relay Bridge — UDP ↔ WebSocket
 *
 * Bridges the hub's UDP binary frames to the browser previewer
 * via WebSocket. This lets you test the full signal pipeline
 * without ESP32 hardware.
 *
 * Architecture:
 *   ds-hub (Rust) → UDP:9200 → [this relay] → WS:9201 → browser previewer
 *   browser → WS:9201 → [this relay] → UDP:9200 → ds-hub
 *
 * Usage:
 *   node relay-bridge.js
 *   Then open previewer with: ?ws=ws://localhost:9201
 */

const dgram = require('dgram');
const { WebSocketServer } = require('ws');
const http = require('http');
const fs = require('fs');
const path = require('path');

const UDP_PORT = 9200;
const WS_PORT = 9201;
const HTTP_PORT = 9876;  // Serve previewer HTML too

// ─── WebSocket Server ───
const wss = new WebSocketServer({ host: '0.0.0.0', port: WS_PORT });
const clients = new Set();

wss.on('connection', (ws, req) => {
    clients.add(ws);
    console.log(`[WS] Client connected (${clients.size} total)`);

    ws.on('message', (data) => {
        // Forward binary messages from browser → UDP (hub)
        if (Buffer.isBuffer(data) || data instanceof ArrayBuffer) {
            const buf = Buffer.from(data);
            udp.send(buf, 0, buf.length, UDP_PORT, '127.0.0.1', (err) => {
                if (err) console.error('[UDP] Send error:', err);
            });
        }
    });

    ws.on('close', () => {
        clients.delete(ws);
        console.log(`[WS] Client disconnected (${clients.size} remaining)`);
    });

    // If we have a cached IR, send it immediately
    if (cachedIR) {
        ws.send(cachedIR);
        console.log('[WS] Sent cached IR to new client');
    }
});

// ─── UDP Receiver ───
const udp = dgram.createSocket('udp4');
let cachedIR = null;  // Cache latest IR push for new WS clients
let hubAddr = null;   // Remember hub address for replies

udp.on('message', (msg, rinfo) => {
    hubAddr = rinfo;

    // Check if this is an IR push (has magic bytes + IR type)
    if (msg.length >= 6 && msg[0] === 0xD5 && msg[1] === 0x7A && msg[2] === 0x40) {
        // Extract and cache the IR JSON
        const len = msg.readUInt16LE(4);
        const json = msg.slice(6, 6 + len).toString();
        cachedIR = json;
        console.log(`[UDP] IR push received (${len} bytes), broadcasting to ${clients.size} WS clients`);

        // Send as JSON text to all WS clients
        for (const ws of clients) {
            if (ws.readyState === 1) ws.send(json);
        }
        return;
    }

    // Forward all other binary frames to WS clients
    for (const ws of clients) {
        if (ws.readyState === 1) ws.send(msg);
    }

    // Log signal updates
    if (msg[0] === 0x20 && msg.length >= 7) {
        const sigId = msg.readUInt16LE(1);
        const value = msg.readInt32LE(3);
        // Only log occasionally to avoid spam
        if (sigId === 0 || value % 10 === 0) {
            console.log(`[UDP] Signal ${sigId} = ${value}`);
        }
    }
});

udp.on('error', (err) => {
    console.error('[UDP] Error:', err);
});

udp.bind(UDP_PORT, () => {
    console.log(`[UDP] Listening on port ${UDP_PORT}`);
});

// ─── HTTP Server (serve previewer) ───
const server = http.createServer((req, res) => {
    let filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url.split('?')[0]);

    if (!fs.existsSync(filePath)) {
        res.writeHead(404);
        res.end('Not found');
        return;
    }

    const ext = path.extname(filePath);
    const contentType = {
        '.html': 'text/html',
        '.js': 'application/javascript',
        '.css': 'text/css',
        '.json': 'application/json',
        '.png': 'image/png',
    }[ext] || 'application/octet-stream';

    res.writeHead(200, { 'Content-Type': contentType });
    fs.createReadStream(filePath).pipe(res);
});

server.listen(HTTP_PORT, '0.0.0.0', () => {
    console.log(`\n  DreamStack Relay Bridge`);
    console.log(`  ─────────────────────────`);
    console.log(`  HTTP:       http://localhost:${HTTP_PORT}/`);
    console.log(`  WebSocket:  ws://localhost:${WS_PORT}`);
    console.log(`  UDP:        port ${UDP_PORT}`);
    console.log(`\n  Open previewer in live mode:`);
    console.log(`  http://localhost:${HTTP_PORT}/index.html?ws=ws://localhost:${WS_PORT}`);
    console.log(`\n  Or file mode (no hub needed):`);
    console.log(`  http://localhost:${HTTP_PORT}/index.html`);
    console.log('');
});
