// Z-AMPP in-house editor — Phase A
// Vanilla JS, no framework. Single track, single video preview, in/out markers per clip.
// Reads assets from /api/v1/assets, streams via /api/v1/assets/:id/stream.

(function () {
  const $ = (id) => document.getElementById(id);

  // ── State ────────────────────────────────────────────────
  const state = {
    assets: [],          // [{ id, display_name, status, duration_ms, ...}]
    filtered: [],
    clips: [],           // [{ uid, assetId, name, src, inSec, outSec, durSec, thumbUrl }]
    selectedClipUid: null,
    isPlaying: false,
    nextUid: 1,
    drafts: loadDrafts(),
  };

  function loadDrafts() {
    try { return JSON.parse(localStorage.getItem('zampp-edit-drafts') || '[]'); }
    catch { return []; }
  }
  function persistDrafts() {
    try { localStorage.setItem('zampp-edit-drafts', JSON.stringify(state.drafts)); } catch {}
  }

    // Thumbnails: /api/v1/assets/:id/thumbnail returns JSON { url }, not the image itself.
  const _thumbCache = new Map();
  async function _thumbFor(id) {
    if (_thumbCache.has(id)) return _thumbCache.get(id);
    const p = (async () => {
      try {
        const r = await fetch('/api/v1/assets/' + id + '/thumbnail', { credentials: 'include' });
        if (!r.ok) return null;
        const j = await r.json();
        return j.url || null;
      } catch { return null; }
    })();
    _thumbCache.set(id, p);
    return p;
  }
  function hydrateThumbnails(root) {
    (root || document).querySelectorAll('img[data-asset-thumb]').forEach(async (img) => {
      if (img.dataset.hydrated === '1') return;
      img.dataset.hydrated = '1';
      const id = img.dataset.assetThumb;
      const url = await _thumbFor(id);
      if (url) img.src = url;
      else img.style.opacity = '0.2';
    });
  }

  // ── API ──────────────────────────────────────────────────
  async function apiGet(path) {
    const r = await fetch('/api/v1' + path, { credentials: 'include' });
    if (!r.ok) throw new Error('HTTP ' + r.status + ' on ' + path);
    return r.json();
  }

  async function loadAssets() {
    try {
      const j = await apiGet('/assets?limit=500');
      state.assets = (j.assets || []).filter(a => a.media_type === 'video' && a.status === 'ready');
      renderAssets();
    } catch (e) {
      $('assetList').innerHTML = '<div class="edit-inspector-empty" style="color:var(--status-red)">Failed: ' + esc(e.message) + '</div>';
    }
  }

  async function streamUrlFor(assetId) {
    const j = await apiGet('/assets/' + assetId + '/stream');
    return j.url;
  }

  // ── Helpers ──────────────────────────────────────────────
  function esc(s) {
    return String(s).replace(/[&<>"']/g, c => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"}[c]));
  }
  function fmtTime(sec) {
    if (sec == null || isNaN(sec)) return '--:--.--';
    sec = Math.max(0, sec);
    const m = Math.floor(sec / 60);
    const s = sec - m * 60;
    return String(m).padStart(2,'0') + ':' + s.toFixed(2).padStart(5,'0');
  }
  function thumbUrl(assetId) { return '/api/v1/assets/' + assetId + '/thumbnail'; }

  // ── Render: asset library ────────────────────────────────
  function renderAssets() {
    const q = ($('assetSearch').value || '').trim().toLowerCase();
    state.filtered = state.assets.filter(a => !q || (a.display_name || '').toLowerCase().includes(q));
    const list = $('assetList');
    if (state.filtered.length === 0) {
      list.innerHTML = '<div class="edit-inspector-empty">' + (q ? 'No matches.' : 'No video assets yet. Upload from Ingest.') + '</div>';
      return;
    }
    list.innerHTML = state.filtered.map(a => {
      const name = a.display_name || a.filename || a.id;
      return '<div class="edit-asset" draggable="true" data-asset-id="' + a.id + '" title="' + esc(name) + '">' +
             '<img class="edit-asset-thumb" data-asset-thumb="' + a.id + '"/>' +
             '<div class="edit-asset-name">' + esc(name) + '</div>' +
             '</div>';
    }).join('');
    hydrateThumbnails(list);
    // Wire drag
    list.querySelectorAll('.edit-asset').forEach(el => {
      el.addEventListener('dragstart', (e) => {
        e.dataTransfer.setData('asset-id', el.dataset.assetId);
        e.dataTransfer.effectAllowed = 'copy';
      });
      // Double-click also adds the clip
      el.addEventListener('dblclick', () => addClipById(el.dataset.assetId));
    });
  }

  // ── Render: timeline track ───────────────────────────────
  function renderTrack() {
    const track = $('track');
    const empty = $('trackEmpty');
    if (state.clips.length === 0) {
      track.innerHTML = '';
      track.appendChild(empty);
      empty.classList.remove('drag-over');
      $('timelineTotal').textContent = '0 clips · 00:00.00';
      return;
    }
    let total = 0;
    track.innerHTML = state.clips.map(c => {
      const trimmed = Math.max(0, c.outSec - c.inSec);
      total += trimmed;
      const inPct  = c.durSec > 0 ? (c.inSec  / c.durSec) * 100 : 0;
      const outPct = c.durSec > 0 ? (c.outSec / c.durSec) * 100 : 100;
      const isActive = c.uid === state.selectedClipUid;
      return '<div class="edit-clip ' + (isActive ? 'active' : '') + '" data-uid="' + c.uid + '" title="' + esc(c.name) + '">' +
             '<button class="edit-clip-remove" data-remove="' + c.uid + '" title="Remove from timeline">✕</button>' +
             '<img class="edit-clip-thumb" data-asset-thumb="' + c.assetId + '"/>' +
             '<div class="edit-clip-bar"><div class="edit-clip-bar-fill" style="left:' + inPct + '%; right:' + (100 - outPct) + '%"></div></div>' +
             '<div class="edit-clip-name">' + esc(c.name) + '</div>' +
             '<div class="edit-clip-meta"><span>' + fmtTime(c.inSec) + '</span><span>' + fmtTime(c.outSec) + '</span></div>' +
             '</div>';
    }).join('');
    hydrateThumbnails(track);
    $('timelineTotal').textContent = state.clips.length + ' clip' + (state.clips.length === 1 ? '' : 's') + ' · ' + fmtTime(total);
    // Wire clicks
    track.querySelectorAll('.edit-clip').forEach(el => {
      el.addEventListener('click', (e) => {
        if (e.target.matches('[data-remove]')) return;
        selectClip(el.dataset.uid);
      });
    });
    track.querySelectorAll('[data-remove]').forEach(btn => {
      btn.addEventListener('click', (e) => {
        e.stopPropagation();
        removeClip(btn.dataset.remove);
      });
    });
  }

  // ── Render: inspector ────────────────────────────────────
  function renderInspector() {
    const host = $('inspector');
    const c = currentClip();
    if (!c) {
      host.innerHTML = '<div class="edit-inspector-empty">Select a clip on the timeline to inspect.</div>';
      return;
    }
    const trimmed = Math.max(0, c.outSec - c.inSec);
    host.innerHTML =
      '<div class="edit-inspector-section">' +
        '<span class="edit-inspector-label">Clip</span>' +
        '<div class="edit-inspector-clipname">' + esc(c.name) + '</div>' +
      '</div>' +
      '<div class="edit-inspector-section">' +
        '<span class="edit-inspector-label">Trim</span>' +
        '<dl class="edit-inspector-stats">' +
          '<dt>In</dt><dd>' + fmtTime(c.inSec)  + '</dd>' +
          '<dt>Out</dt><dd>' + fmtTime(c.outSec) + '</dd>' +
          '<dt>Length</dt><dd>' + fmtTime(trimmed) + '</dd>' +
          '<dt>Source</dt><dd>' + fmtTime(c.durSec) + '</dd>' +
        '</dl>' +
      '</div>' +
      '<div class="edit-inspector-section">' +
        '<span class="edit-inspector-label">Actions</span>' +
        '<div class="edit-inspector-actions">' +
          '<button class="btn btn-ghost btn-sm" id="ipReset">Reset trim</button>' +
          '<button class="btn btn-ghost btn-sm" id="ipMoveL">← Move</button>' +
          '<button class="btn btn-ghost btn-sm" id="ipMoveR">Move →</button>' +
          '<button class="btn btn-ghost btn-sm" id="ipDup">Duplicate</button>' +
          '<button class="btn btn-ghost btn-sm" id="ipSplit">Split at playhead</button>' +
          '<button class="btn btn-danger btn-sm" id="ipRemove" style="margin-left:auto">Remove</button>' +
        '</div>' +
      '</div>';
    $('ipReset').onclick  = () => { c.inSec = 0; c.outSec = c.durSec; refreshAll(); };
    $('ipRemove').onclick = () => removeClip(c.uid);
    $('ipMoveL').onclick  = () => moveClip(c.uid, -1);
    $('ipMoveR').onclick  = () => moveClip(c.uid,  1);
    $('ipDup').onclick    = () => duplicateClip(c.uid);
    const sp = $('ipSplit'); if (sp) sp.onclick = () => splitClip(c.uid, $('previewVideo').currentTime);
  }

  // ── Selection / preview ──────────────────────────────────
  function currentClip() {
    return state.clips.find(c => c.uid === state.selectedClipUid) || null;
  }

  async function selectClip(uid) {
    state.selectedClipUid = uid;
    renderTrack();
    renderInspector();
    const c = currentClip();
    if (!c) return showEmptyPreview();
    if (!c.src) {
      try { c.src = await streamUrlFor(c.assetId); }
      catch (e) { console.warn('Stream lookup failed:', e); }
    }
    showPreview(c);
  }

  function showEmptyPreview() {
    $('previewEmpty').style.display = 'flex';
    const v = $('previewVideo');
    v.style.display = 'none';
    v.removeAttribute('src');
    v.pause();
    $('btnPlay').disabled = true;
    $('btnSetIn').disabled = true;
    $('btnSetOut').disabled = true;
    $('btnSplit').disabled = true;
    $('scrubber').disabled = true;
    $('timeDisplay').textContent = '--:--.-- / --:--.--';
  }

  function showPreview(c) {
    $('previewEmpty').style.display = 'none';
    const v = $('previewVideo');
    v.style.display = 'block';
    if (v.dataset.src !== c.src) {
      v.src = c.src;
      v.dataset.src = c.src;
    }
    $('btnPlay').disabled = false;
    $('btnSetIn').disabled = false;
    $('btnSetOut').disabled = false;
    $('btnSplit').disabled = false;
    $('scrubber').disabled = false;
    updateScrubberMarkers(c);
  }

  function updateScrubberMarkers(c) {
    const sc = $('scrubber');
    const dur = c.durSec || $('previewVideo').duration || 0;
    const inPct  = dur > 0 ? (c.inSec  / dur) * 100 : 0;
    const outPct = dur > 0 ? (c.outSec / dur) * 100 : 100;
    sc.style.setProperty('--in-pct', inPct  + '%');
    sc.style.setProperty('--out-pct', outPct + '%');
  }

  // ── Mutations ────────────────────────────────────────────
  async function addClipById(assetId) {
    const a = state.assets.find(x => x.id === assetId);
    if (!a) return;
    let src;
    try { src = await streamUrlFor(assetId); } catch (e) { console.warn(e); }
    const durSec = (a.duration_ms || 0) / 1000;
    const uid = 'c' + (state.nextUid++);
    const c = {
      uid, assetId,
      name: a.display_name || a.filename || a.id,
      src,
      inSec: 0,
      outSec: durSec || 0,
      durSec: durSec || 0,
    };
    state.clips.push(c);
    state.selectedClipUid = uid;
    refreshAll();
    // If we didn't have duration metadata, load the video to grab it
    if (!c.durSec && src) {
      try {
        const probe = document.createElement('video');
        probe.preload = 'metadata';
        probe.src = src;
        await new Promise((res, rej) => { probe.onloadedmetadata = res; probe.onerror = rej; setTimeout(rej, 8000); });
        c.durSec = probe.duration || 0;
        c.outSec = c.outSec || c.durSec;
        refreshAll();
      } catch {}
    }
  }

  function removeClip(uid) {
    state.clips = state.clips.filter(c => c.uid !== uid);
    if (state.selectedClipUid === uid) state.selectedClipUid = null;
    refreshAll();
  }

  function splitClip(uid, atSec) {
    const i = state.clips.findIndex(c => c.uid === uid);
    if (i < 0) return;
    const c = state.clips[i];
    const dur = c.durSec || (atSec + 0.05);
    const cut = Math.max(c.inSec + 0.05, Math.min(c.outSec - 0.05, atSec));
    const left  = Object.assign({}, c, { uid: 'c' + (state.nextUid++), outSec: cut });
    const right = Object.assign({}, c, { uid: 'c' + (state.nextUid++), inSec: cut });
    state.clips.splice(i, 1, left, right);
    state.selectedClipUid = right.uid;
    refreshAll();
  }

  function moveClip(uid, dir) {
    const i = state.clips.findIndex(c => c.uid === uid);
    if (i < 0) return;
    const j = i + dir;
    if (j < 0 || j >= state.clips.length) return;
    [state.clips[i], state.clips[j]] = [state.clips[j], state.clips[i]];
    refreshAll();
  }

  function duplicateClip(uid) {
    const c = state.clips.find(x => x.uid === uid);
    if (!c) return;
    const i = state.clips.indexOf(c);
    const copy = Object.assign({}, c, { uid: 'c' + (state.nextUid++) });
    state.clips.splice(i + 1, 0, copy);
    state.selectedClipUid = copy.uid;
    refreshAll();
  }

  function refreshAll() {
    renderTrack();
    renderInspector();
    const c = currentClip();
    if (c) updateScrubberMarkers(c);
  }

  // ── Preview transport ────────────────────────────────────
  function bindPreview() {
    const v = $('previewVideo');
    const sc = $('scrubber');
    const t  = $('timeDisplay');

    v.addEventListener('loadedmetadata', () => {
      const c = currentClip();
      if (!c) return;
      if (!c.durSec) { c.durSec = v.duration || 0; if (!c.outSec) c.outSec = c.durSec; refreshAll(); }
      sc.max = v.duration;
      t.textContent = fmtTime(v.currentTime) + ' / ' + fmtTime(v.duration);
      updateScrubberMarkers(c);
    });
    v.addEventListener('timeupdate', () => {
      const c = currentClip();
      if (!c) return;
      sc.value = v.currentTime;
      t.textContent = fmtTime(v.currentTime) + ' / ' + fmtTime(v.duration);
      // Loop within in/out markers (soft — pause at out)
      if (state.isPlaying && c.outSec > 0 && v.currentTime >= c.outSec) {
        v.pause();
        state.isPlaying = false;
        updatePlayButton();
      }
    });
    v.addEventListener('play',  () => { state.isPlaying = true;  updatePlayButton(); });
    v.addEventListener('pause', () => { state.isPlaying = false; updatePlayButton(); });
    sc.addEventListener('input', () => { v.currentTime = parseFloat(sc.value) || 0; });

    $('btnPlay').addEventListener('click', () => {
      if (v.paused) { if (v.currentTime < (currentClip()?.inSec || 0)) v.currentTime = currentClip().inSec; v.play(); }
      else v.pause();
    });
    $('btnSetIn').addEventListener('click', () => {
      const c = currentClip(); if (!c) return;
      c.inSec = Math.min(v.currentTime, c.outSec - 0.05);
      refreshAll();
    });
    $('btnSplit').addEventListener('click', () => { const c = currentClip(); if (c) splitClip(c.uid, $('previewVideo').currentTime); });
    $('btnSetOut').addEventListener('click', () => {
      const c = currentClip(); if (!c) return;
      c.outSec = Math.max(v.currentTime, c.inSec + 0.05);
      refreshAll();
    });

    document.addEventListener('keydown', (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
      if (e.key === ' ') { e.preventDefault(); $('btnPlay').click(); }
      else if (e.key === 'i' || e.key === 'I') { $('btnSetIn').click(); }
      else if (e.key === 'o' || e.key === 'O') { $('btnSetOut').click(); }
      else if (e.key === 'b' || e.key === 'B' || e.key === 's' || e.key === 'S') { const c = currentClip(); if (c) splitClip(c.uid, $('previewVideo').currentTime); }
      else if (e.key === 'Delete' || e.key === 'Backspace') {
        if (state.selectedClipUid) removeClip(state.selectedClipUid);
      } else if (e.key === 'ArrowLeft' && e.altKey)  { if (state.selectedClipUid) moveClip(state.selectedClipUid, -1); }
      else  if (e.key === 'ArrowRight' && e.altKey) { if (state.selectedClipUid) moveClip(state.selectedClipUid,  1); }
    });
  }

  function updatePlayButton() {
    const btn = $('btnPlay');
    btn.innerHTML = state.isPlaying
      ? '<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><rect x="3" y="3" width="4" height="10"/><rect x="9" y="3" width="4" height="10"/></svg>'
      : '<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><polygon points="4,3 13,8 4,13"/></svg>';
  }

  // ── Drop target ──────────────────────────────────────────
  function bindDrop() {
    const track = $('track');
    const empty = $('trackEmpty');
    ['dragenter', 'dragover'].forEach(ev => {
      track.addEventListener(ev, (e) => {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';
        if (empty.parentNode === track) empty.classList.add('drag-over');
      });
    });
    ['dragleave'].forEach(ev => {
      track.addEventListener(ev, () => empty.classList.remove('drag-over'));
    });
    track.addEventListener('drop', (e) => {
      e.preventDefault();
      empty.classList.remove('drag-over');
      const id = e.dataTransfer.getData('asset-id');
      if (id) addClipById(id);
    });
  }

  // ── Draft save ───────────────────────────────────────────
    window.exportEDL = async function () {
    if (state.clips.length === 0) { alert('Add some clips to the timeline first.'); return; }
    const total = state.clips.reduce((a, c) => a + (c.outSec - c.inSec), 0);
    const ok = confirm('Queue this edit for render?\n\n' + state.clips.length + ' clips, ' + fmtTime(total) + ' total.\n\nServer-side render is queued for the next phase; for now your edit is saved as a draft you can restore.');
    if (!ok) return;
    saveEDL();
  };

  window.saveEDL = function () {
    const edl = {
      created_at: new Date().toISOString(),
      clips: state.clips.map(c => ({
        assetId: c.assetId,
        name: c.name,
        inSec: c.inSec,
        outSec: c.outSec,
      })),
    };
    state.drafts.unshift(edl);
    state.drafts = state.drafts.slice(0, 20);
    persistDrafts();
    // Tiny inline toast
    const t = document.createElement('div');
    t.textContent = 'Draft saved locally (' + edl.clips.length + ' clip' + (edl.clips.length === 1 ? '' : 's') + ')';
    Object.assign(t.style, {
      position: 'fixed', bottom: '24px', right: '24px',
      padding: '10px 14px', background: 'oklch(15% 0.025 250)',
      border: '1px solid oklch(45% 0.20 266 / 0.5)', borderRadius: '6px',
      color: 'var(--text-primary)', fontSize: '13px', zIndex: '999',
      boxShadow: '0 10px 30px -8px oklch(0% 0 0 / 0.5)',
    });
    document.body.appendChild(t);
    setTimeout(() => t.remove(), 2500);
  };

  // ── Init ─────────────────────────────────────────────────
  $('assetSearch').addEventListener('input', renderAssets);
  bindPreview();
  bindDrop();
  loadAssets();

  // Optional: read ?asset=<id> from URL and auto-add it
  const params = new URLSearchParams(location.search);
  const seed = params.get('asset');
  if (seed) {
    // Wait for assets to load
    const check = setInterval(() => {
      if (state.assets.length > 0) {
        clearInterval(check);
        addClipById(seed);
      }
    }, 200);
    setTimeout(() => clearInterval(check), 5000);
  }
})();
