// server.js  (CommonJS)
const express = require('express');
const cors = require('cors');
const { SerialPort } = require('serialport');

const API_PORT   = Number(process.env.API_PORT || 8787);
const SILENCE_MS = Number(process.env.RADIN_SILENCE_MS || 50);
let   portPath   = process.env.RADIN_PORT || 'COM8';
let   baudRate   = Number(process.env.RADIN_BAUD || 19200);

let port = null;
let buffer = '';
let latestWeight = null;
let latestRaw = null;
let lastUpdateAt = null;
let silenceTimer = null;

function parseFrame(frame) {
    if (!frame) return;
    latestRaw = frame;
    const nums = frame.replace(',', '.').match(/-?\d+(?:\.\d+)?/g);
    if (!nums) return;
    const v = parseFloat(nums[nums.length - 1]);
    latestWeight = Math.round(v * 1000) / 1000;
    lastUpdateAt = new Date();
}

function feedBufferAndParse(txt) {
    buffer += txt;

    // split on CR/LF/spaces; flush intermediate parts
    const parts = buffer.split(/\r\n|\r|\n| +/);
    if (parts.length > 1) {
        buffer = parts.pop();
        for (const p of parts) if (p.trim()) parseFrame(p.trim());
    }

    // also flush on short silence to catch continuous streams
    clearTimeout(silenceTimer);
    silenceTimer = setTimeout(() => {
        const f = buffer.trim();
        buffer = '';
        if (f) parseFrame(f);
    }, SILENCE_MS);
}

async function openSerial() {
    if (port && port.isOpen) return;
    port = new SerialPort({ path: portPath, baudRate, autoOpen: false });
    port.on('error', e => console.log('[serial error]', e.message));
    port.on('close', () => {
        console.log('[serial] closed; retrying in 2s');
        setTimeout(() => openSerial().catch(() => {}), 2000);
    });
    await new Promise((res, rej) => port.open(err => (err ? rej(err) : res())));
    console.log(`[serial] opened ${portPath} @ ${baudRate}`);
    port.on('data', buf => {
        const txt = buf.toString('utf8');
        // Uncomment for debugging raw input:
        // console.log('[serial raw]', JSON.stringify(txt));
        feedBufferAndParse(txt);
    });
}

function closeSerial() {
    try { clearTimeout(silenceTimer); } catch {}
    if (port && port.isOpen) {
        try { port.removeAllListeners('data'); } catch {}
        try { port.close(); } catch {}
    }
    port = null;
}

const app = express();
app.use(cors());
app.use(express.json());

// ---- HTTP API ----
app.get('/status', (_req, res) => {
    res.json({ connected: !!(port && port.isOpen), port: portPath, baudRate, lastUpdateAt });
});

app.get('/ports', async (_req, res) => {
    const list = await SerialPort.list();
    res.json(list.map(p => ({
        path: p.path,
        manufacturer: p.manufacturer || null,
        vendorId: p.vendorId || null,
        productId: p.productId || null
    })));
});

app.post('/connect', async (req, res) => {
    try {
        if (req.body?.path) portPath = req.body.path;
        if (req.body?.baud) baudRate = Number(req.body.baud) || baudRate;
        closeSerial();
        await openSerial();
        res.json({ ok: true, port: portPath, baudRate });
    } catch (e) { res.status(500).json({ ok: false, error: e.message }); }
});

app.post('/disconnect', (_req, res) => { closeSerial(); res.json({ ok: true }); });

app.get('/weight', (_req, res) => { res.json({ kg: latestWeight, updatedAt: lastUpdateAt }); });
app.get('/raw',    (_req, res) => { res.json({ raw: latestRaw,  updatedAt: lastUpdateAt }); });

app.listen(API_PORT, () => console.log(`[api] http://127.0.0.1:${API_PORT}`));

// Auto-open on start if RADIN_PORT is set
if (portPath) openSerial().catch(err => console.log('[serial open failed]', err.message));
