'use strict';

// ── Architektur ───────────────────────────────────────────────────────────────
// Der Server (Node) besitzt die Kameras und liefert den Live-Stream als
// MJPEG multipart/x-mixed-replace unter /api/stream/<id>. Der Browser rendert
// das nativ in einem <img>. KEIN WebRTC, KEIN go2rtc, kein Transcode.
//
// HD-Snapshot: GET /api/snapshot/<id>/hires. Der Server-Schalter pausiert dafür
// den Live-FFmpeg kurz (~1–2 s), greift 1280×960, schaltet zurück. Der <img>-
// Stream friert in dieser Zeit ein und läuft danach weiter – kein Client-Handling
// nötig (das war früher die Fehlerquelle).

const P = '[WebcamViewer]';
const log = (c, m) => console.log(`${P}[${c}] ${m}`);
const warn = (c, m) => console.warn(`${P}[${c}] ⚠ ${m}`);
const logErr = (c, m, e) => console.error(`${P}[${c}] ✗ ${m}`, e ?? '');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const cameras = []; // { id, box, img, infoEl, toggleBtn, hdBtn, active, busy }

// ── Live-Stream an/aus ────────────────────────────────────────────────────────
function startStream(cam) {
  cam.active = true;
  // Cache-Buster erzwingt eine frische Verbindung (sonst hängt Reconnect manchmal)
  cam.img.src = `/api/stream/${encodeURIComponent(cam.id)}?t=${Date.now()}`;
  cam.toggleBtn.textContent = '⏸';
  cam.toggleBtn.title = 'Stream ausschalten';
  setInfo(cam, 'verbindet…', '');
  log(cam.id, 'Live an');
}

function stopStream(cam) {
  cam.active = false;
  cam.img.removeAttribute('src'); // schließt die multipart-Verbindung
  cam.toggleBtn.textContent = '▶';
  cam.toggleBtn.title = 'Stream einschalten';
  setInfo(cam, 'aus', '');
  log(cam.id, 'Live aus');
}

// ── Snapshot-Modus (stream:false): alle 5 s ein Einzelbild ──────────────────
// Kein Video – der Server öffnet das Gerät pro Snapshot kurz (one-shot). Fehler
// (z. B. Gerät gerade durch HD-Grab belegt) werden still übersprungen, das letzte
// gute Bild bleibt stehen.
const SNAPSHOT_INTERVAL_MS = 5000;

async function fetchSnapshot(cam) {
  if (!cam.snapshotActive) return;
  try {
    const r = await fetch(`/api/snapshot/${encodeURIComponent(cam.id)}?t=${Date.now()}`,
      { signal: AbortSignal.timeout(8000) });
    if (!r.ok) { setInfo(cam, `Snapshot-Fehler (HTTP ${r.status})`, 'warn'); return; }
    const blob = await r.blob();
    const url = URL.createObjectURL(blob);
    cam.img.src = url;
    if (cam.lastBlobUrl) URL.revokeObjectURL(cam.lastBlobUrl);
    cam.lastBlobUrl = url;
    setInfo(cam, `Einzelbild · ${new Date().toLocaleTimeString()}`, 'ok');
  } catch (_e) {
    setInfo(cam, 'Snapshot-Timeout', 'warn');
  }
}

function startSnapshotMode(cam) {
  cam.snapshotActive = true;
  setInfo(cam, 'Einzelbild lädt…', '');
  fetchSnapshot(cam); // sofort das erste Bild
  cam.snapTimer = setInterval(() => fetchSnapshot(cam), SNAPSHOT_INTERVAL_MS);
  log(cam.id, `Snapshot-Modus (alle ${SNAPSHOT_INTERVAL_MS / 1000} s)`);
}

// ── HD-Snapshot ───────────────────────────────────────────────────────────────
async function runHiresGrab(cam) {
  if (cam.busy) return;
  cam.busy = true;
  cam.hdBtn.disabled = true;
  setInfo(cam, cam.stream ? 'HD: erfasse… (Stream friert kurz)' : 'HD: erfasse…', 'warn');
  log(cam.id, '── HD-Grab gestartet ──');

  let blobUrl = null;
  try {
    const r = await fetch(`/api/snapshot/${encodeURIComponent(cam.id)}/hires`, { signal: AbortSignal.timeout(20000) });
    if (!r.ok) {
      const body = await r.json().catch(() => ({}));
      throw new Error(body.error ?? `HTTP ${r.status}`);
    }
    const blob = await r.blob();
    const width = r.headers.get('X-Frame-Width') || '?';
    blobUrl = URL.createObjectURL(blob);

    const a = document.createElement('a');
    a.href = blobUrl;
    a.download = `${cam.id}_hires_${Date.now()}.jpg`;
    document.body.appendChild(a);
    a.click();
    a.remove();

    setInfo(cam, `HD gespeichert (${width}px)`, 'ok');
    log(cam.id, `HD-Grab OK – ${blob.size} bytes, ${width}px`);
  } catch (e) {
    logErr(cam.id, 'HD-Grab fehlgeschlagen', e);
    setInfo(cam, `HD Fehler: ${e.message}`, 'crit');
  } finally {
    if (blobUrl) setTimeout(() => URL.revokeObjectURL(blobUrl), 10000);
    cam.busy = false;
    cam.hdBtn.disabled = false;
    log(cam.id, '── HD-Grab beendet ──');
  }
}

// ── HD-Snapshot aller Kameras (parallel) ──────────────────────────────────────
// cam0/cam1 liegen auf getrennten Geräten → der Schalter grabbt beide parallel
// gefahrlos (jeder Schalter steuert nur sein eigenes Gerät).
async function snapshotAllHires() {
  const snapBtn = document.getElementById('snapAllBtn');
  if (snapBtn) snapBtn.disabled = true;
  log('snap', `HD-Grab alle: ${cameras.map((c) => c.id).join(', ')}`);
  try {
    await Promise.allSettled(cameras.map((c) => runHiresGrab(c)));
  } finally {
    if (snapBtn) snapBtn.disabled = false;
    log('snap', '── HD-Grab alle beendet ──');
  }
}

// ── Status-Anzeige ────────────────────────────────────────────────────────────
function setInfo(cam, text, cls) {
  cam.infoEl.textContent = text;
  cam.infoEl.className = 'cam-info ' + (cls ?? '');
}

// ── Kamera-View aufbauen ──────────────────────────────────────────────────────
// camMeta = { id, name, position, stream, hires }
function buildCamera(camMeta, container) {
  const box = document.createElement('div');
  box.className = 'cam-box';

  const labelText = camMeta.name + (camMeta.position ? ` · ${camMeta.position}` : '');
  const label = document.createElement('div');
  label.className = 'cam-label';
  label.textContent = labelText;

  const info = document.createElement('div');
  info.className = 'cam-info';
  info.textContent = camMeta.stream ? '…' : 'Nur Snapshot';

  const hd = document.createElement('button');
  hd.className = 'cam-hdtest';
  hd.textContent = 'HD';

  const cam = { id: camMeta.id, stream: camMeta.stream, box, infoEl: info, hdBtn: hd, active: false, busy: false };

  hd.onclick = () => runHiresGrab(cam);

  if (camMeta.stream) {
    const img = document.createElement('img');
    img.className = 'cam-img';
    img.alt = labelText;
    img.addEventListener('load', () => { if (cam.active && !cam.busy) setInfo(cam, 'MJPEG · live', 'ok'); });
    img.addEventListener('error', () => {
      if (!cam.active) return;
      setInfo(cam, 'Verbindungsfehler – neu…', 'crit');
      setTimeout(() => { if (cam.active && !cam.busy) startStream(cam); }, 2000);
    });

    const toggle = document.createElement('button');
    toggle.className = 'cam-toggle';
    toggle.onclick = () => { if (!cam.busy) (cam.active ? stopStream(cam) : startStream(cam)); };

    cam.img = img;
    cam.toggleBtn = toggle;
    hd.title = 'Hi-Res-Snapshot (1280×960) – Live friert kurz ein, dann Download';

    box.appendChild(img);
    box.appendChild(toggle);
    startStream(cam);
  } else {
    // Snapshot-Modus: grosser Banner + Einzelbild, das alle 5 s aktualisiert wird.
    const banner = document.createElement('div');
    banner.className = 'single-pic-banner';
    banner.textContent = 'Single Picture no Video';

    const img = document.createElement('img');
    img.className = 'cam-img';
    img.alt = labelText;

    cam.img = img;
    hd.title = 'Hi-Res-Snapshot – Download';

    box.appendChild(banner);
    box.appendChild(img);
    startSnapshotMode(cam);
  }

  box.appendChild(label);
  box.appendChild(info);
  box.appendChild(hd);
  container.appendChild(box);

  cameras.push(cam);
}

// ── Init ──────────────────────────────────────────────────────────────────────
async function init() {
  log('init', 'Starte...');
  const container = document.getElementById('cameras');
  const statusText = document.getElementById('statusText');

  let camList = [];
  try {
    const r = await fetch('/api/snapshot');
    log('init', `/api/snapshot → HTTP ${r.status}`);
    if (r.ok) camList = (await r.json()).cameras ?? [];
    log('init', `Kameras: ${camList.map((c) => c.id).join(', ') || '(keine)'}`);
  } catch (e) {
    logErr('init', '/api/snapshot Fehler – Fallback', e);
  }
  if (camList.length === 0) {
    warn('init', 'Fallback cam0, cam1');
    camList = [
      { id: 'cam0', name: 'cam0', position: '', stream: true, hires: true },
      { id: 'cam1', name: 'cam1', position: '', stream: true, hires: true },
    ];
  }

  const snapBtn = document.getElementById('snapAllBtn');
  if (snapBtn) { snapBtn.onclick = snapshotAllHires; snapBtn.disabled = false; }

  camList.forEach((c) => buildCamera(c, container));
  statusText.textContent = `${camList.length} Kamera${camList.length !== 1 ? 's' : ''} · MJPEG`;
  log('init', 'Fertig');
}

init();
