'use strict';

const express = require('express');
const { readJpegWidth } = require('./cameraSwitch');

// Stabile Schnittstellen für Viewer und Homing-Projekt – lesen NUR aus den
// CameraSwitch-Instanzen (RAM-Puffer + Event-Stream). Kein Gerätezugriff hier,
// keine go2rtc-Abhängigkeit mehr.
//
//   GET /api/snapshot              → JSON-Liste der Kameras
//   GET /api/snapshot/cam0         → letztes Live-JPEG (640) aus dem Puffer
//   GET /api/snapshot/cam0/hires   → HD-JPEG (1280): Schalter pausiert Live kurz
//   GET /api/stream/cam0           → MJPEG multipart/x-mixed-replace (Live)

function createSnapshotRouter(switches, cameras) {
  const router = express.Router();

  // cameras = camsMeta aus server.js: [{id, name, position, stream, hires, encode, mseCodec, note}]
  router.get('/', (_req, res) => {
    res.json({
      cameras: cameras.map((c) => ({
        id: c.id,
        name: c.name,
        position: c.position,
        stream: c.stream,
        hires: c.hires,
        encode: c.encode,        // 'copybsf' | 'mjpeg' | 'h264' → Viewer wählt Player
        mseCodec: c.mseCodec,    // nur bei h264: Codec-String für MSE-addSourceBuffer
        url: `/api/snapshot/${c.id}`,
      })),
    });
  });

  // HD-Grab: delegiert an den Schalter. Der Schalter garantiert, dass Live
  // sauber gestoppt ist (Prozess-close), bevor 1280 startet → kein Race.
  router.get('/:id/hires', async (req, res) => {
    const sw = switches[req.params.id];
    if (!sw) return res.status(404).json({ error: `Unbekannte Kamera: ${req.params.id}` });
    try {
      const jpeg = await sw.grabHires();
      res.set({
        'Content-Type': 'image/jpeg',
        'Content-Length': jpeg.length,
        'Cache-Control': 'no-store',
        'X-Camera-Id': req.params.id,
        'X-Frame-Width': String(readJpegWidth(jpeg) ?? ''),
        'X-Timestamp': new Date().toISOString(),
      });
      res.end(jpeg);
    } catch (err) {
      res.status(503).json({ error: `hires: ${err.message}` });
    }
  });

  router.get('/:id', async (req, res) => {
    const sw = switches[req.params.id];
    if (!sw) return res.status(404).json({ error: `Unbekannte Kamera: ${req.params.id}` });
    try {
      // grabSnapshot(): liefert das Live-Frame falls vorhanden, sonst (Snapshot-Modus,
      // stream:false) ein one-shot Bild – öffnet das Gerät kurz und schliesst es wieder.
      const frame = await sw.grabSnapshot();
      res.set({
        'Content-Type': 'image/jpeg',
        'Content-Length': frame.length,
        'Cache-Control': 'no-store',
        'X-Camera-Id': req.params.id,
        'X-Timestamp': new Date().toISOString(),
      });
      res.end(frame);
    } catch (err) {
      res.status(503).json({ error: `kein Frame: ${err.message}` });
    }
  });

  return router;
}


// H.264-Live-Stream als fortlaufendes fragmentiertes MP4 (video/mp4). Ein FFmpeg
// (im Schalter) → Fan-out an beliebig viele Browser. Der Client speist die Bytes
// per MSE in ein <video>. Jeder neue Client bekommt ZUERST das gecachte
// Init-Segment (ftyp+moov), dann ab dem nächsten Fragment den Live-Stream.
function streamH264(sw, req, res) {
  res.writeHead(200, {
    'Content-Type': 'video/mp4',
    'Cache-Control': 'no-cache, no-store, must-revalidate',
    'Pragma': 'no-cache',
    'Connection': 'close',
    'X-Camera-Id': req.params.id,
  });
  if (res.socket) res.socket.setNoDelay(true);

  let closed = false;
  const cleanup = () => {
    if (closed) return;
    closed = true;
    sw.removeListener('segment', onSegment);
    sw.removeListener('init', onReinit);
    sw.release();
  };

  const onSegment = (seg) => {
    if (closed) return;
    // Video-Fragmente dürfen NICHT einzeln gedroppt werden (Decode-Lücke) – ist ein
    // Client zu langsam, lieber die Verbindung kappen; der Browser verbindet neu.
    if (res.writableLength > (4 << 20)) { cleanup(); try { res.end(); } catch (_e) {} return; }
    try { res.write(seg); } catch (_e) { cleanup(); }
  };

  // Neues Init-Segment ⇒ der Producer wurde neu gestartet (Auflösung/Encoder/Crash).
  // Das alte moov passt nicht mehr → Verbindung beenden, Client holt sich frisch ab.
  const onReinit = () => { if (!closed) { cleanup(); try { res.end(); } catch (_e) {} } };

  sw.acquire(); // On-Demand: startet den Producer falls nötig

  const start = (init) => {
    if (closed) return;
    try { res.write(init); } catch (_e) { cleanup(); return; }
    sw.on('segment', onSegment);
    sw.on('init', onReinit);
  };

  if (sw.initSegment) {
    start(sw.initSegment);
  } else {
    // Auf das erste Init-Segment warten (Producer läuft warm).
    const t = setTimeout(() => { sw.removeListener('init', onFirst); if (!closed) { sw.release(); try { res.end(); } catch (_e) {} } }, 10000);
    const onFirst = (init) => { clearTimeout(t); start(init); };
    sw.once('init', onFirst);
  }

  req.on('close', cleanup);
  res.on('error', cleanup);
}

// MJPEG-Live-Stream als multipart/x-mixed-replace. Ein FFmpeg (im Schalter) →
// Fan-out an beliebig viele Browser. Browser rendert das nativ im <img>.
function createStreamRouter(switches) {
  const router = express.Router();

  router.get('/:id', (req, res) => {
    const sw = switches[req.params.id];
    if (!sw) return res.status(404).end();

    // H.264-Kameras liefern fMP4 (MSE) statt MJPEG-multipart (<img>).
    if (sw.encode === 'h264') return streamH264(sw, req, res);

    res.writeHead(200, {
      'Content-Type': 'multipart/x-mixed-replace; boundary=frame',
      'Cache-Control': 'no-cache, no-store, must-revalidate',
      'Pragma': 'no-cache',
      'Connection': 'close',
      'X-Camera-Id': req.params.id,
    });
    if (res.socket) res.socket.setNoDelay(true); // Nagle aus → kein Sammel-Delay

    let closed = false;
    const cleanup = () => {
      if (closed) return;
      closed = true;
      sw.removeListener('frame', onFrame);
      sw.release(); // On-Demand: Verbraucher abmelden
    };

    const onFrame = (buf) => {
      if (closed) return;
      // Backpressure: langsamer Client bremst die anderen nicht – Frames droppen
      if (res.writableLength > (1 << 20)) return;
      // cork/uncork: Header+JPEG+Trailer als EIN TCP-Segment → minimale Latenz.
      // try/catch: ein kaputter Client darf die anderen nicht aushungern.
      try {
        res.cork();
        res.write(`--frame\r\nContent-Type: image/jpeg\r\nContent-Length: ${buf.length}\r\n\r\n`);
        res.write(buf);
        res.write('\r\n');
        res.uncork();
      } catch (_e) {
        cleanup();
      }
    };

    sw.acquire(); // On-Demand: Verbraucher anmelden (startet Live falls nötig)
    sw.on('frame', onFrame);
    if (sw.latest) onFrame(sw.latest); // sofort erstes Bild, falls schon eins da

    req.on('close', cleanup);
    res.on('error', cleanup);
  });

  return router;
}

// GET /api/cameras → vollständige Kamera-Metadaten (ohne device-Pfad)
function createCamerasRouter(cameras) {
  const router = express.Router();
  router.get('/', (_req, res) => {
    res.json({
      cameras: cameras.map(({ device: _d, ...rest }) => rest),
    });
  });
  return router;
}

module.exports = { createSnapshotRouter, createStreamRouter, createCamerasRouter };
