/* ===========================================================================
   iRaven App Store Studio — Render on server (headless).

   Unlike studio-render-server.jsx (which renders each frame in the
   browser and uploads them as JPEGs), this path uploads the timeline
   DEFINITION + raw media assets. The server then spins up a headless
   Chromium that loads /studio/render-headless.html, drives it
   frame-by-frame, and ffmpeg image2 assembles the output.

   Use this when client resources are the bottleneck — slow laptop, big
   timeline, complex animations — and you'd rather wait longer for the
   server to do the work at full quality.

   Public API:
     window.renderTimelineHeadless({ preview, audioKey, preset, fps, onProgress })
       → { blob, validation, frameCount, durationSeconds }
   =========================================================================== */

(function () {
  /* Walk the preview tree and collect every media key referenced by
     scenes or audio. We use these keys to upload the corresponding
     blobs from IndexedDB. */
  function collectMediaKeys(preview) {
    const keys = new Set();
    for (const v of [].concat(preview)) {
      if (!v) continue;
      for (const sc of v.scenes || []) {
        const m = sc.media;
        if (m && typeof m === 'object' && m.key) keys.add(m.key);
      }
      if (v.audio && v.audio.key) keys.add(v.audio.key);
    }
    return [...keys];
  }

  function audioExtFromBlob(blob) {
    const t = (blob && blob.type) || '';
    if (t.includes('mpeg')) return 'mp3';
    if (t.includes('aac') || t.includes('mp4')) return 'm4a';
    if (t.includes('wav')) return 'wav';
    if (t.includes('webm')) return 'webm';
    if (t.includes('ogg') || t.includes('opus')) return 'ogg';
    return 'bin';
  }

  function mediaExtFromBlob(blob) {
    const t = (blob && blob.type) || '';
    if (t.includes('mp4') || t.includes('quicktime')) return 'mp4';
    if (t.includes('webm')) return 'webm';
    if (t.includes('mov')) return 'mov';
    if (t.includes('png')) return 'png';
    if (t.includes('jpeg') || t.includes('jpg')) return 'jpg';
    if (t.includes('webp')) return 'webp';
    return 'bin';
  }

  async function renderTimelineHeadless({ preview, audioKey, preset, fps, onProgress }) {
    if (!preview) throw new Error('preview required');

    const usePreset = preset || (window.APP_STORE_DEFAULT_PRESET || 'iphone-6-5-portrait');
    const useFps = fps || 30;

    if (onProgress) onProgress({ phase: 'gathering' });
    const form = new FormData();
    form.append('preview', new Blob([JSON.stringify(preview)], { type: 'application/json' }));
    form.append('preset', usePreset);
    form.append('fps', String(useFps));

    /* Pull every media key out of IndexedDB and append as `media[<key>]`.
       The filename keeps a sensible extension so the server's static
       file route can set the right Content-Type for the headless
       Chromium to play. */
    const keys = collectMediaKeys(preview);
    if (onProgress) onProgress({ phase: 'uploading-media', total: keys.length });
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      try {
        const blob = await window.vidMediaGet(key);
        if (!blob) continue;
        const ext = mediaExtFromBlob(blob);
        const filename = `${key}.${ext}`;
        form.append(`media[${key}]`, blob, filename);
        if (onProgress) onProgress({ phase: 'uploading-media', i: i + 1, total: keys.length, key });
      } catch (e) {
        console.warn('skip media (load failed)', key, e);
      }
    }

    /* The audio key is also among `keys` above so the server already has
       it as a media[<key>]. We additionally label it as the dedicated
       `audio` field so the headless route maps it onto ffmpeg's audio
       input regardless of which key the studio assigned. */
    if (audioKey) {
      try {
        const blob = await window.vidMediaGet(audioKey);
        if (blob) {
          const ext = audioExtFromBlob(blob);
          form.append('audio', blob, `audio.${ext}`);
        }
      } catch (e) { console.warn('audio fetch failed', e); }
    }

    if (onProgress) onProgress({ phase: 'submitting' });

    const res = await fetch('/api/render-headless', { method: 'POST', body: form });
    if (!res.ok) {
      const text = await res.text().catch(() => '');
      throw new Error(`headless render failed (${res.status}): ${text.slice(0, 200)}`);
    }
    const vHeader = res.headers.get('x-validation');
    let validation = null;
    try { validation = vHeader ? JSON.parse(vHeader) : null; }
    catch (e) { validation = null; }
    const frameCount = Number(res.headers.get('x-frame-count') || 0);
    const durationSeconds = Number(res.headers.get('x-duration') || 0);
    const blob = await res.blob();
    if (onProgress) onProgress({ phase: 'done', frameCount, durationSeconds });
    return { blob, validation, frameCount, durationSeconds };
  }

  Object.assign(window, { renderTimelineHeadless });
})();
