'use strict';

// ── Hires-Grab-Diagnose — läuft DIREKT auf dem Host, ohne Docker/Express ──────
//
// Zweck: herausfinden, welche Auflösung /dev/videoN beim Grab WIRKLICH liefert,
// entkoppelt vom Container (kein Sync-Lag, kein Redeploy). Loggt JEDEN Frame mit
// Breite×Höhe und Bytes, speichert den besten als /tmp/hires-probe.jpg.
//
// Aufruf (auf dem ThinkCentre, im Projektverzeichnis):
//   node tools/hires-probe.js /dev/video4 1920x1080 mjpeg
//   node tools/hires-probe.js /dev/video4 1920x1080 copybsf
//   node tools/hires-probe.js /dev/video0 1280x960  copybsf
//
// Args:  <device>  <size WxH>  <encode: mjpeg|copybsf>  [framerate=15]  [frames=12]

const { spawn } = require('child_process');
const fs = require('fs');

const device = process.argv[2] || '/dev/video4';
const size = process.argv[3] || '1920x1080';
const encode = process.argv[4] || 'mjpeg';
const fps = process.argv[5] || '15';
const maxFrames = parseInt(process.argv[6] || '12', 10);

// SOF-Marker lesen: Höhe bei +5, Breite bei +7
function readJpegDims(buf) {
  let i = 2;
  while (i < buf.length - 8) {
    if (buf[i] !== 0xFF) break;
    const marker = buf[i + 1];
    const segLen = buf.readUInt16BE(i + 2);
    if (marker === 0xC0 || marker === 0xC2) {
      return { h: buf.readUInt16BE(i + 5), w: buf.readUInt16BE(i + 7) };
    }
    i += 2 + segLen;
  }
  return { h: null, w: null };
}

// mpjpeg-Parser (identisch zur App: keyt auf Content-Length)
class MpjpegParser {
  constructor(onFrame) { this.onFrame = onFrame; this.buf = Buffer.alloc(0); this.need = -1; }
  push(chunk) {
    this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;
    for (;;) {
      if (this.need < 0) {
        const he = this.buf.indexOf('\r\n\r\n');
        if (he < 0) return;
        const m = /content-length:\s*(\d+)/i.exec(this.buf.toString('latin1', 0, he));
        if (!m) { this.buf = this.buf.subarray(he + 4); continue; }
        this.need = parseInt(m[1], 10);
        this.buf = this.buf.subarray(he + 4);
      }
      if (this.buf.length < this.need) return;
      const f = this.buf.subarray(0, this.need);
      this.buf = this.buf.subarray(this.need);
      this.need = -1;
      try { this.onFrame(f); } catch (_e) {}
    }
  }
}

// EXAKT die Args, die die App im hires-Pfad nutzt (videoOutArgs):
const outArgs = encode === 'mjpeg'
  ? ['-c:v', 'mjpeg', '-q:v', '5']
  : ['-c:v', 'copy', '-bsf:v', 'mjpeg2jpeg'];

const args = [
  '-hide_banner', '-loglevel', 'warning',
  '-f', 'v4l2', '-input_format', 'mjpeg',
  '-video_size', size, '-framerate', fps,
  '-i', device,
  ...outArgs, '-f', 'mpjpeg', 'pipe:1',
];

console.log(`\n[probe] device=${device} size=${size} encode=${encode} fps=${fps}`);
console.log(`[probe] ffmpeg ${args.join(' ')}\n`);

const p = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'pipe'] });

let n = 0;
let best = null;
const parser = new MpjpegParser((frame) => {
  n++;
  const { w, h } = readJpegDims(frame);
  console.log(`[probe] frame ${String(n).padStart(2)}  ${String(w)}x${String(h)}  ${frame.length} bytes`);
  if (!best || frame.length > best.length) best = Buffer.from(frame);
  if (n >= maxFrames) finish();
});

p.stdout.on('data', (c) => parser.push(c));
p.stderr.on('data', (c) => process.stderr.write(`[ffmpeg] ${c}`));
p.on('error', (e) => { console.error('[probe] spawn-Fehler:', e.message); process.exit(1); });

let finished = false;
function finish() {
  if (finished) return; finished = true;
  try { p.kill('SIGKILL'); } catch (_e) {}
  if (best) {
    const { w, h } = readJpegDims(best);
    fs.writeFileSync('/tmp/hires-probe.jpg', best);
    console.log(`\n[probe] ERGEBNIS: bester Frame ${w}x${h}, ${best.length} bytes → /tmp/hires-probe.jpg`);
  } else {
    console.log('\n[probe] ERGEBNIS: KEIN Frame empfangen');
  }
  process.exit(0);
}
setTimeout(finish, 8000); // Sicherheitsnetz
