/**
 * VibeLoop Live – Cloudflare Stream (WHIP/WHEP, <1s Latenz, unbegrenzte Zuschauer)
 * Fallback: R2-Segment-Broadcast wenn Stream nicht konfiguriert.
 */
const VibeLoopLive = (function () {
  const ICE = [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }];
  const SEGMENT_MS = 2000;
  const POLL_MS = 1200;

  let hostStream = null;
  let whipPc = null;
  let whipResource = null;
  let recorder = null;
  let segmentSeq = 0;
  let uploadBusy = false;
  let watchPoll = null;
  let whepPc = null;
  let watchQueue = [];
  let watchPlaying = false;
  let watchLastSeq = 0;
  let liveMode = 'none';

  function waitIceGathering(pc, ms) {
    return new Promise((resolve) => {
      if (pc.iceGatheringState === 'complete') {
        resolve();
        return;
      }
      const done = () => {
        if (pc.iceGatheringState === 'complete') {
          pc.removeEventListener('icegatheringstatechange', done);
          resolve();
        }
      };
      pc.addEventListener('icegatheringstatechange', done);
      setTimeout(resolve, ms || 3500);
    });
  }

  function blobToDataUrl(blob) {
    return new Promise((resolve, reject) => {
      const r = new FileReader();
      r.onload = () => resolve(r.result);
      r.onerror = reject;
      r.readAsDataURL(blob);
    });
  }

  function pickMime() {
    const types = ['video/webm;codecs=vp8,opus', 'video/webm;codecs=vp9,opus', 'video/webm'];
    for (const t of types) {
      if (MediaRecorder.isTypeSupported(t)) return t;
    }
    return '';
  }

  async function uploadSegment(liveId, api, blob) {
    if (!blob || !blob.size || uploadBusy) return;
    uploadBusy = true;
    const seq = segmentSeq++;
    try {
      const dataUrl = await blobToDataUrl(blob);
      await api.uploadLiveSegment(liveId, { seq, dataUrl });
    } catch (_) {
      segmentSeq = Math.max(0, seq);
    }
    uploadBusy = false;
  }

  async function startWhip(whipUrl, videoEl) {
    hostStream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },
      audio: true,
    });
    if (videoEl) {
      videoEl.srcObject = hostStream;
      videoEl.muted = true;
      videoEl.play().catch(() => {});
    }
    whipPc = new RTCPeerConnection({ iceServers: ICE });
    hostStream.getTracks().forEach((t) => whipPc.addTrack(t, hostStream));
    const offer = await whipPc.createOffer();
    await whipPc.setLocalDescription(offer);
    await waitIceGathering(whipPc);
    const res = await fetch(whipUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/sdp' },
      body: whipPc.localDescription.sdp,
    });
    if (!res.ok) throw new Error('WHIP-Verbindung fehlgeschlagen');
    whipResource = res.headers.get('Location') || res.headers.get('location') || '';
    const answerSdp = await res.text();
    await whipPc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
    liveMode = 'stream';
    return hostStream;
  }

  async function startSegmentBroadcast(liveId, videoEl, api) {
    hostStream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },
      audio: true,
    });
    if (videoEl) {
      videoEl.srcObject = hostStream;
      videoEl.muted = true;
      videoEl.play().catch(() => {});
    }
    const mime = pickMime();
    if (!mime) throw new Error('MediaRecorder nicht unterstützt');
    segmentSeq = 0;
    recorder = new MediaRecorder(hostStream, { mimeType: mime, videoBitsPerSecond: 1_200_000 });
    recorder.ondataavailable = (e) => {
      if (e.data && e.data.size) uploadSegment(liveId, api, e.data);
    };
    recorder.start(SEGMENT_MS);
    liveMode = 'broadcast';
    return hostStream;
  }

  async function startBroadcast({ liveId, videoEl, api, whipUrl }) {
    stopBroadcast();
    if (whipUrl) {
      try {
        return await startWhip(whipUrl, videoEl);
      } catch (e) {
        console.warn('WHIP fehlgeschlagen, Fallback Broadcast:', e);
      }
    }
    return startSegmentBroadcast(liveId, videoEl, api);
  }

  function stopBroadcast() {
    if (whipResource) {
      fetch(whipResource, { method: 'DELETE' }).catch(() => {});
      whipResource = null;
    }
    if (whipPc) {
      whipPc.close();
      whipPc = null;
    }
    if (recorder && recorder.state !== 'inactive') {
      try {
        recorder.stop();
      } catch (_) {}
    }
    recorder = null;
    if (hostStream) hostStream.getTracks().forEach((t) => t.stop());
    hostStream = null;
    segmentSeq = 0;
    uploadBusy = false;
    liveMode = 'none';
  }

  function segmentCount() {
    return segmentSeq;
  }

  function getMode() {
    return liveMode;
  }

  async function startWhep(whepUrl, videoEl) {
    whepPc = new RTCPeerConnection({ iceServers: ICE });
    whepPc.addTransceiver('video', { direction: 'recvonly' });
    whepPc.addTransceiver('audio', { direction: 'recvonly' });
    whepPc.ontrack = (e) => {
      if (!videoEl || !e.streams[0]) return;
      videoEl.srcObject = e.streams[0];
      videoEl.classList.remove('hidden');
      videoEl.muted = false;
      videoEl.play().catch(() => {
        videoEl.muted = true;
        videoEl.play().catch(() => {});
      });
    };
    const offer = await whepPc.createOffer();
    await whepPc.setLocalDescription(offer);
    await waitIceGathering(whepPc);
    const res = await fetch(whepUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/sdp' },
      body: whepPc.localDescription.sdp,
    });
    if (!res.ok) throw new Error('WHEP-Verbindung fehlgeschlagen');
    const answerSdp = await res.text();
    await whepPc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
  }

  async function tryPlayNext(videoEl) {
    if (watchPlaying || !videoEl || !watchQueue.length) return;
    const seg = watchQueue.shift();
    if (seg.seq <= watchLastSeq) {
      tryPlayNext(videoEl);
      return;
    }
    watchPlaying = true;
    videoEl.classList.remove('hidden');
    videoEl.src = seg.url;
    videoEl.onended = () => {
      watchLastSeq = seg.seq;
      watchPlaying = false;
      tryPlayNext(videoEl);
    };
    videoEl.onerror = () => {
      watchPlaying = false;
      tryPlayNext(videoEl);
    };
    try {
      await videoEl.play();
      return true;
    } catch (_) {
      watchPlaying = false;
      setTimeout(() => tryPlayNext(videoEl), 400);
      return false;
    }
  }

  async function watchBroadcast(liveId, videoEl, api, onConnected, onFallback, fallbackTimer) {
    watchLastSeq = 0;
    watchQueue = [];
    watchPlaying = false;
    let connected = false;
    watchPoll = setInterval(async () => {
      try {
        const j = await api.getLiveSegments(liveId, { after: watchLastSeq });
        for (const s of j.segments || []) {
          if (s.seq > watchLastSeq && !watchQueue.some((q) => q.seq === s.seq)) {
            watchQueue.push(s);
            watchQueue.sort((a, b) => a.seq - b.seq);
          }
        }
        if (!connected && watchQueue.length) {
          const ok = await tryPlayNext(videoEl);
          if (ok) {
            connected = true;
            clearTimeout(fallbackTimer);
            if (onConnected) onConnected();
          }
        } else tryPlayNext(videoEl);
      } catch (_) {}
    }, POLL_MS);
  }

  async function watch({ liveId, videoEl, api, whepUrl, onConnected, onFallback }) {
    stopWatch();
    let connected = false;
    const fallbackTimer = setTimeout(() => {
      if (!connected && onFallback) onFallback();
    }, 10000);

    if (whepUrl) {
      try {
        await startWhep(whepUrl, videoEl);
        connected = true;
        clearTimeout(fallbackTimer);
        if (onConnected) onConnected();
        return () => clearTimeout(fallbackTimer);
      } catch (e) {
        console.warn('WHEP fehlgeschlagen, Fallback Broadcast:', e);
      }
    }

    await watchBroadcast(liveId, videoEl, api, onConnected, onFallback, fallbackTimer);
    return () => clearTimeout(fallbackTimer);
  }

  function stopWatch() {
    if (watchPoll) clearInterval(watchPoll);
    watchPoll = null;
    if (whepPc) whepPc.close();
    whepPc = null;
    watchQueue = [];
    watchPlaying = false;
    watchLastSeq = 0;
  }

  return { startBroadcast, stopBroadcast, watch, stopWatch, segmentCount, getMode };
})();

if (typeof window !== 'undefined') window.VibeLoopLive = VibeLoopLive;
