'use strict';

// ── Hardware-Encoding-Konfiguration (Intel / AMD / Software) ──────────────────
// Eine Stelle, die entscheidet WIE H.264 erzeugt wird. Der Rest des Codes kennt
// nur `encode: 'h264'` (pro Kamera) – WELCHER Encoder (GPU-Vendor) genutzt wird,
// ist eine Maschinen-Eigenschaft und kommt global aus dem Env (server.js).
//
//   GPU=intel  → VAAPI (h264_vaapi)   ← Default-Encoder, läuft auf Intel UHD 630
//   GPU=amd    → VAAPI (h264_vaapi)   ← läuft auf AMD 680M (VCN), gleiche API
//   GPU=auto   → VAAPI (Annahme: /dev/dri vorhanden)
//   GPU=none   → libx264 (Software-Fallback, zum Testen ohne GPU; hohe CPU)
//
// VAAPI ist die gemeinsame Linux-Schnittstelle für Intel UND AMD → ein Codepfad
// für beide. Optional erzwingbar: HWENC=vaapi|qsv|libx264 (qsv = nur Intel).

const DEFAULT_DEVICE = '/dev/dri/renderD128';

// Normalisiert die Env-Wünsche zu einem konkreten Encoder-Backend.
//   vendor:  'intel' | 'amd' | 'auto' | 'none'        (GPU)
//   encoder: 'vaapi' | 'qsv' | 'libx264' | undefined  (HWENC, überschreibt vendor)
//   device:  VAAPI-Renderknoten (HWENC_DEVICE)
function resolveHwenc({ vendor = 'auto', encoder, device } = {}) {
  const v = String(vendor).toLowerCase();
  let enc = encoder ? String(encoder).toLowerCase() : null;

  if (!enc) {
    if (v === 'none' || v === 'cpu' || v === 'software') enc = 'libx264';
    else enc = 'vaapi'; // intel, amd, auto → VAAPI (gemeinsamer Pfad)
  }
  if (!['vaapi', 'qsv', 'libx264'].includes(enc)) {
    throw new Error(`Unbekannter HWENC '${enc}' (erlaubt: vaapi, qsv, libx264)`);
  }
  return { encoder: enc, device: device || DEFAULT_DEVICE, vendor: v };
}

// profile ('constrained_baseline'|'baseline'|'main'|'high') + level-Hex →
// MSE-Codec-String (z. B. 'avc1.4D401F'). Der Browser braucht das exakt für
// addSourceBuffer(); der Stream-Decode selbst ist toleranter.
const PROFILE_IDC = {
  constrained_baseline: '42E0',
  baseline: '4200',
  main: '4D40',
  high: '6400',
};
function mseCodecString(profile = 'main', levelHex = '1F') {
  const idc = PROFILE_IDC[String(profile).toLowerCase()] ?? PROFILE_IDC.main;
  const lvl = String(levelHex).toUpperCase().padStart(2, '0');
  return `avc1.${idc}${lvl}`;
}

// Baut die FFmpeg-Args für den Live-H.264-Pfad:
//   Ausgang 1 (pipe:1): fragmentiertes MP4 (fMP4) → MSE im Browser.
//   Ausgang 2 (pipe:3): optional gedrosseltes MJPEG → hält `latest` für
//     /api/snapshot am Leben (Homing-Projekt) und ermöglicht <img>-Fallback.
//
// Rückgabe: { args, useFd3 }  – useFd3=true ⇒ spawn mit stdio …,'pipe' (fd 3).
function h264LiveArgs({
  device, size, fps, hwenc,
  bitrate = '3M', gop, profile = 'main',
  fragMs = 200,
  jpegSnapshots = true, jpegFps = 2, jpegQ = 7,
}) {
  const g = gop || Math.max(1, Math.round((parseInt(fps, 10) || 30) * 2)); // ~2 s
  const { encoder, device: vaapiDev } = hwenc;

  const input = [
    '-hide_banner', '-loglevel', 'warning',
    '-fflags', 'nobuffer',
    '-f', 'v4l2', '-input_format', 'mjpeg',
    '-video_size', size, '-framerate', String(fps),
    '-i', device,
  ];

  // Encoder-spezifische Geräte-Init + Upload-/Format-Filter.
  let hwInit = [];
  let encFilter;          // Filter, der einen encode-fähigen Frame erzeugt
  let encCodec;           // -c:v …
  if (encoder === 'vaapi') {
    hwInit = ['-vaapi_device', vaapiDev];
    encFilter = 'format=nv12,hwupload';
    encCodec = ['-c:v', 'h264_vaapi'];
  } else if (encoder === 'qsv') {
    hwInit = ['-init_hw_device', `qsv=hw:${vaapiDev}`, '-filter_hw_device', 'hw'];
    encFilter = 'format=nv12,hwupload=extra_hw_frames=16';
    encCodec = ['-c:v', 'h264_qsv'];
  } else { // libx264 (Software-Fallback)
    encFilter = 'format=yuv420p';
    encCodec = ['-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency'];
  }

  const mp4Out = [
    ...encCodec, '-profile:v', profile, '-b:v', String(bitrate),
    '-g', String(g), '-bf', '0',
    '-f', 'mp4',
    '-movflags', '+frag_keyframe+empty_moov+default_base_moof',
    '-frag_duration', String(Math.max(1, Math.round(fragMs)) * 1000),
    'pipe:1',
  ];

  if (!jpegSnapshots) {
    return { args: [...input, ...hwInit, '-vf', encFilter, ...mp4Out], useFd3: false };
  }

  // Mit Snapshot-Nebenausgang: Eingang splitten – ein Zweig encodet H.264,
  // der andere liefert gedrosseltes MJPEG für /api/snapshot + Fallback.
  const filterComplex = `[0:v]split=2[enc][jpg];[enc]${encFilter}[venc]`;
  const args = [
    ...input,
    ...hwInit,
    '-filter_complex', filterComplex,
    '-map', '[venc]', ...mp4Out,
    '-map', '[jpg]', '-r', String(jpegFps), '-c:v', 'mjpeg', '-q:v', String(jpegQ),
    '-f', 'mpjpeg', 'pipe:3',
  ];
  return { args, useFd3: true };
}

module.exports = { resolveHwenc, mseCodecString, h264LiveArgs, DEFAULT_DEVICE };
