'use strict';

// ── Parser für fragmentiertes MP4 (fMP4) aus FFmpegs `-f mp4 +empty_moov` ─────
// FFmpeg schreibt auf pipe:1:
//   ftyp  moov                        ← Init-Segment (einmal, am Anfang)
//   moof  mdat  moof  mdat  …         ← je ein Media-Fragment (moof+mdat)
//
// MSE im Browser braucht das Init-Segment ZUERST, dann komplette Fragmente.
// Ein spät verbundener Client bekommt das gecachte Init-Segment und steigt am
// nächsten Fragment ein – deshalb trennen wir hier sauber auf Box-Grenzen.
//
// Box-Layout (ISO-BMFF): [4B size][4B type][payload]. size deckt size+type ab.
//   size == 1 → 64-Bit largesize (8B nach dem type). size == 0 → bis EOF (im
//   Live-Stream nicht zu erwarten; wir warten dann auf mehr Daten).
class Fmp4Parser {
  // onInit(buf)    – einmal, sobald ftyp+moov vollständig sind
  // onSegment(buf) – je ein vollständiges Fragment (moof+mdat)
  constructor({ onInit, onSegment } = {}) {
    this.onInit = onInit || (() => {});
    this.onSegment = onSegment || (() => {});
    this.buf = Buffer.alloc(0);
    this.gotInit = false;
    this.initParts = []; // sammelt ftyp…moov
    this.fragParts = []; // sammelt moof…mdat des aktuellen Fragments
  }

  push(chunk) {
    this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;
    for (;;) {
      if (this.buf.length < 8) return; // Header unvollständig
      let size = this.buf.readUInt32BE(0);
      let headerLen = 8;
      if (size === 1) {
        if (this.buf.length < 16) return;
        // 64-Bit-Größe: high32 muss 0 sein (Boxen hier sind klein)
        size = this.buf.readUInt32BE(12);
        headerLen = 16;
      } else if (size === 0) {
        return; // Box bis EOF – im Live-Stream nicht erwartet, auf mehr warten
      }
      if (size < headerLen) { // defekt → diese Box-Länge überspringen, resynchronisieren
        this.buf = this.buf.subarray(headerLen);
        continue;
      }
      if (this.buf.length < size) return; // Box-Body noch nicht komplett

      const type = this.buf.toString('latin1', 4, 8);
      const box = this.buf.subarray(0, size);
      this.buf = this.buf.subarray(size);

      if (!this.gotInit) {
        this.initParts.push(box);
        if (type === 'moov') {
          const init = Buffer.concat(this.initParts);
          this.initParts = [];
          this.gotInit = true;
          try { this.onInit(init); } catch (_e) { /* Consumer-Fehler ignorieren */ }
        }
        continue;
      }

      // Media-Phase: Fragment = moof … mdat. Bei mdat ist es vollständig.
      this.fragParts.push(box);
      if (type === 'mdat') {
        const seg = Buffer.concat(this.fragParts);
        this.fragParts = [];
        try { this.onSegment(seg); } catch (_e) { /* Consumer-Fehler ignorieren */ }
      }
    }
  }
}

module.exports = { Fmp4Parser };
