// services/web-ui/public/js/timeline.js
// DOM-based NLE timeline engine.  Exposes: window.Timeline
// Depends on: window.TC (timecode.js must be loaded first)

(function (global) {
  'use strict';

  const TRACK_HEIGHT = 48;  // px, each track row
  const HEADER_W     = 40;  // px, track label column width
  const MIN_CLIP_PX  = 4;   // minimum rendered clip width

  const TRACKS = [
    { id: 0,   label: 'V1', type: 'video' },
    { id: 1,   label: 'V2', type: 'video' },
    { id: 100, label: 'A1', type: 'audio' },
    { id: 101, label: 'A2', type: 'audio' },
  ];

  // ── Internal state ──────────────────────────────────────────────────────────
  const s = {
    container:       null,
    rulerEl:         null,
    tracksEl:        null,
    playheadEl:      null,
    clips:           [],       // working copy; each has a local .id
    scale:           100,      // px per second
    fps:             59.94,
    playheadFrames:  0,
    activeTool:      'select', // 'select' | 'razor' | 'hand'
    selectedId:      null,
    contextMenu:     null,
    onClipsChanged:  null,     // callback(clips[])
    onPlayheadMoved: null,     // callback(frames)
    onClipContextMenu: null,   // callback({clip, x, y})
    onExternalDrop:  null,     // callback({track, timelineFrames, dataTransfer})
  };

  let _uid = 0;
  function uid() { return 'tl_' + (++_uid); }
  function framesToPx(f)  { return (f / s.fps) * s.scale; }
  function pxToFrames(px) { return Math.round((px / s.scale) * s.fps); }

  // ── init ────────────────────────────────────────────────────────────────────

  function init(container, options) {
    options = options || {};
    s.container       = container;
    s.fps             = options.fps             || 59.94;
    s.scale           = options.scale           || 100;
    s.onClipsChanged  = options.onClipsChanged  || null;
    s.onPlayheadMoved = options.onPlayheadMoved || null;
    s.onClipContextMenu = options.onClipContextMenu || null;
    s.onExternalDrop  = options.onExternalDrop  || null;

    container.innerHTML   = '';
    container.style.cssText = [
      'position:relative', 'overflow-x:auto', 'overflow-y:hidden',
      'user-select:none', 'display:flex', 'flex-direction:column',
    ].join(';');

    // Ruler row
    s.rulerEl = document.createElement('div');
    s.rulerEl.className  = 'tl-ruler';
    s.rulerEl.style.cssText = [
      'position:sticky', 'top:0', 'z-index:10',
      'height:24px', 'flex-shrink:0',
      'background:var(--bg-base)', 'border-bottom:1px solid var(--border)',
      'cursor:pointer',
    ].join(';');
    s.rulerEl.addEventListener('click', _onRulerClick);
    container.appendChild(s.rulerEl);

    // Tracks wrapper (clips + playhead live here)
    s.tracksEl = document.createElement('div');
    s.tracksEl.style.cssText = 'position:relative;flex:1;';
    TRACKS.forEach(function (t) {
      var row  = document.createElement('div');
      row.className = 'tl-track-row';
      row.style.cssText = [
        'display:flex', 'height:' + TRACK_HEIGHT + 'px',
        'border-bottom:1px solid var(--border)',
      ].join(';');

      // Sticky label
      var hdr = document.createElement('div');
      hdr.className = 'tl-track-hdr';
      hdr.textContent = t.label;
      hdr.style.cssText = [
        'width:' + HEADER_W + 'px', 'flex-shrink:0',
        'display:flex', 'align-items:center', 'justify-content:center',
        'font-size:10px', 'font-weight:600', 'letter-spacing:.05em',
        'color:var(--text-tertiary)', 'background:var(--bg-panel)',
        'border-right:1px solid var(--border)',
        'position:sticky', 'left:0', 'z-index:5',
      ].join(';');
      row.appendChild(hdr);

      // Clip area
      var area = document.createElement('div');
      area.className = 'tl-clip-area';
      area.dataset.trackId = t.id;
      area.style.cssText = 'flex:1;position:relative;overflow:visible;';
      area.addEventListener('click', _onAreaClick);
      area.addEventListener('dragover', _onAreaDragOver);
      area.addEventListener('drop', _onAreaDrop);
      row.appendChild(area);

      s.tracksEl.appendChild(row);
    });

    // Playhead
    s.playheadEl = document.createElement('div');
    s.playheadEl.className = 'tl-playhead';
    s.playheadEl.style.cssText = [
      'position:absolute', 'top:0', 'bottom:0',
      'width:2px', 'background:var(--accent)',
      'pointer-events:none', 'z-index:20',
    ].join(';');
    s.tracksEl.appendChild(s.playheadEl);

    container.appendChild(s.tracksEl);

    // Zoom: Ctrl+wheel
    container.addEventListener('wheel', function (e) {
      if (!e.ctrlKey) return;
      e.preventDefault();
      var delta = e.deltaY < 0 ? 1.15 : 0.87;
      s.scale = Math.min(500, Math.max(20, s.scale * delta));
      _renderClips();
      _renderRuler();
    }, { passive: false });

    // Keyboard shortcuts (Delete, tool keys)
    document.removeEventListener('keydown', _onKeyDown);
    document.addEventListener('keydown', _onKeyDown);

    _renderRuler();
    _initHandPan();
  }

  // ── Ruler ───────────────────────────────────────────────────────────────────

  function _renderRuler() {
    s.rulerEl.innerHTML = '';
    var totalSecs = 600; // 10 minutes
    var totalPx   = HEADER_W + totalSecs * s.scale;

    var canvas    = document.createElement('canvas');
    canvas.width  = totalPx;
    canvas.height = 24;
    canvas.style.cssText = 'display:block;width:' + totalPx + 'px;height:24px;';
    var ctx = canvas.getContext('2d');

    var bgColor = getComputedStyle(document.documentElement)
      .getPropertyValue('--bg-base').trim() || '#0d0f14';
    var borderColor = getComputedStyle(document.documentElement)
      .getPropertyValue('--border').trim() || '#2a2d35';
    var textColor = getComputedStyle(document.documentElement)
      .getPropertyValue('--text-tertiary').trim() || '#5a6070';

    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, totalPx, 24);

    // Tick interval: coarser when zoomed out
    var tickSecs = s.scale < 30 ? 10 : s.scale < 60 ? 5 : s.scale < 120 ? 2 : 1;

    ctx.strokeStyle = borderColor;
    ctx.fillStyle   = textColor;
    ctx.font        = '9px Inter, sans-serif';
    ctx.textAlign   = 'left';

    for (var t = 0; t <= totalSecs; t += tickSecs) {
      var x = HEADER_W + t * s.scale;
      ctx.beginPath();
      ctx.moveTo(x, 18);
      ctx.lineTo(x, 24);
      ctx.stroke();
      if (t % (tickSecs * 5) === 0) {
        var mm  = String(Math.floor(t / 60)).padStart(2, '0');
        var ss  = String(t % 60).padStart(2, '0');
        ctx.fillText(mm + ':' + ss, x + 2, 14);
      }
    }
    s.rulerEl.appendChild(canvas);
  }

  function _onRulerClick(e) {
    var rect   = s.rulerEl.getBoundingClientRect();
    var x      = e.clientX - rect.left - HEADER_W + s.container.scrollLeft;
    if (x < 0) return;
    var frames = pxToFrames(x);
    setPlayhead(frames);
    if (s.onPlayheadMoved) s.onPlayheadMoved(frames);
  }

  // ── Render clips ────────────────────────────────────────────────────────────

  function render(clips, options) {
    options = options || {};
    s.clips = clips.map(function (c) { return Object.assign({}, c, { _id: c._id || uid() }); });
    if (options.fps) s.fps = options.fps;
    _renderClips();
  }

  function _renderClips() {
    TRACKS.forEach(function (t) {
      var area = s.tracksEl.querySelector('.tl-clip-area[data-track-id="' + t.id + '"]');
      if (!area) return;
      area.innerHTML = '';
      s.clips
        .filter(function (c) { return c.track === t.id; })
        .sort(function (a, b) { return a.timeline_in_frames - b.timeline_in_frames; })
        .forEach(function (clip) {
          area.appendChild(_makeClipEl(clip, t));
        });
    });
    _positionPlayhead();
  }

  function _makeClipEl(clip, track) {
    var left  = framesToPx(clip.timeline_in_frames);
    var width = Math.max(MIN_CLIP_PX, framesToPx(clip.timeline_out_frames - clip.timeline_in_frames));
    var isSelected = clip._id === s.selectedId;

    var el = document.createElement('div');
    el.className    = 'tl-clip';
    el.dataset.clipId = clip._id;
    el.style.cssText = [
      'position:absolute',
      'left:'  + left  + 'px',
      'width:' + width + 'px',
      'height:' + (TRACK_HEIGHT - 2) + 'px',
      'top:1px',
      'border-radius:3px',
      'box-sizing:border-box',
      'overflow:hidden',
      'display:flex',
      'align-items:center',
      'padding:0 6px',
      'background:' + (track.type === 'video'
        ? 'oklch(55% 0.15 52 / 0.22)'
        : 'oklch(55% 0.14 280 / 0.22)'),
      'border:1px solid ' + (isSelected
        ? 'var(--accent)'
        : 'var(--border-strong)'),
      'cursor:' + (s.activeTool === 'razor' ? 'crosshair' : 'default'),
    ].join(';');

    // Clip label
    var lbl = document.createElement('span');
    lbl.textContent = clip.display_name || clip.name || 'Clip';
    lbl.style.cssText = [
      'font-size:9px', 'font-weight:500',
      'color:var(--text-secondary)',
      'overflow:hidden', 'text-overflow:ellipsis', 'white-space:nowrap',
      'pointer-events:none', 'flex:1',
    ].join(';');
    el.appendChild(lbl);

    el.addEventListener('contextmenu', function (e) {
      e.preventDefault();
      e.stopPropagation();
      s.selectedId = clip._id;
      _renderClips();
      if (s.onClipContextMenu) s.onClipContextMenu({ clip: Object.assign({}, clip), x: e.clientX, y: e.clientY });
    });

    if (s.activeTool === 'select') {
      // Trim handles (shown on hover)
      var lh = _makeHandle('left');
      var rh = _makeHandle('right');
      el.appendChild(lh);
      el.appendChild(rh);

      el.addEventListener('mouseenter', function () {
        lh.style.opacity = '0.8';
        rh.style.opacity = '0.8';
      });
      el.addEventListener('mouseleave', function () {
        lh.style.opacity = '0';
        rh.style.opacity = '0';
      });

      // Drag to move (body only)
      el.addEventListener('mousedown', function (e) {
        if (e.button !== 0) return;
        if (e.target === lh || e.target === rh) return;
        e.preventDefault();
        s.selectedId = clip._id;
        // Update selection highlight without destroying the element we're dragging
        s.tracksEl.querySelectorAll('.tl-clip').forEach(function (c) {
          c.style.borderColor = c.dataset.clipId === clip._id ? 'var(--accent)' : 'var(--border-strong)';
        });
        _onMoveStart(e, clip);
      });

      lh.addEventListener('mousedown', function (e) {
        if (e.button !== 0) return;
        e.stopPropagation();
        _onTrimStart(e, clip, 'left');
      });
      rh.addEventListener('mousedown', function (e) {
        if (e.button !== 0) return;
        e.stopPropagation();
        _onTrimStart(e, clip, 'right');
      });
    }

    if (s.activeTool === 'razor') {
      el.addEventListener('click', function (e) { _onRazorClick(e, clip); });
    }

    return el;
  }

  function _makeHandle(side) {
    var h = document.createElement('div');
    h.style.cssText = [
      'position:absolute',
      side + ':0',
      'top:0', 'bottom:0',
      'width:6px',
      'cursor:' + (side === 'left' ? 'w-resize' : 'e-resize'),
      'background:var(--accent)',
      'opacity:0',
      'transition:opacity 0.12s',
    ].join(';');
    return h;
  }

  // ── Select tool: move ───────────────────────────────────────────────────────

  function _onMoveStart(e, clip) {
    var startX    = e.clientX;
    var origIn    = clip.timeline_in_frames;
    var origOut   = clip.timeline_out_frames;
    var duration  = origOut - origIn;

    function onMove(ev) {
      var dx      = ev.clientX - startX;
      var dFrames = pxToFrames(dx);
      clip.timeline_in_frames  = Math.max(0, origIn  + dFrames);
      clip.timeline_out_frames = clip.timeline_in_frames + duration;
      _renderClips();
    }
    function onUp() {
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup',   onUp);
      if (s.onClipsChanged) s.onClipsChanged(s.clips.slice());
    }
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup',   onUp);
  }

  // ── Select tool: trim ───────────────────────────────────────────────────────

  function _onTrimStart(e, clip, side) {
    var startX      = e.clientX;
    var origTlIn    = clip.timeline_in_frames;
    var origTlOut   = clip.timeline_out_frames;
    var origSrcIn   = clip.source_in_frames;
    var origSrcOut  = clip.source_out_frames;
    // Full asset duration in frames (null when unknown)
    var assetFrames = clip.duration_ms
      ? Math.round((clip.duration_ms / 1000) * s.fps)
      : null;

    function onMove(ev) {
      var dx  = ev.clientX - startX;
      var dFr = pxToFrames(dx);
      if (side === 'left') {
        var newTlIn = Math.max(0, Math.min(origTlIn + dFr, origTlOut - 1));
        var dIn     = newTlIn - origTlIn;
        clip.timeline_in_frames = newTlIn;
        clip.source_in_frames   = Math.max(0, origSrcIn + dIn);
      } else {
        var newTlOut = Math.max(origTlIn + 1, origTlOut + dFr);
        var dOut     = newTlOut - origTlOut;
        // When we know the asset duration, don't extend past the source boundary
        if (assetFrames !== null) {
          var maxOut = assetFrames - origSrcOut;
          if (dOut > maxOut) dOut = maxOut;
        }
        clip.timeline_out_frames = origTlOut + dOut;
        clip.source_out_frames   = assetFrames !== null
          ? Math.min(assetFrames, origSrcOut + dOut)
          : origSrcOut + dOut;
      }
      _renderClips();
    }
    function onUp() {
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup',   onUp);
      if (s.onClipsChanged) s.onClipsChanged(s.clips.slice());
    }
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup',   onUp);
  }

  // ── Razor tool ──────────────────────────────────────────────────────────────

  function _onRazorClick(e, clip) {
    // Frame at the click position within the clip element
    var clipRect      = e.currentTarget.getBoundingClientRect();
    var xWithinClip   = e.clientX - clipRect.left;
    var framesInClip  = pxToFrames(xWithinClip);
    var clipDuration  = clip.timeline_out_frames - clip.timeline_in_frames;

    // Guard: don't split at the very edge
    if (framesInClip <= 0 || framesInClip >= clipDuration) return;

    var splitTlFrame  = clip.timeline_in_frames + framesInClip;
    var splitSrcFrame = clip.source_in_frames   + framesInClip;

    var left = Object.assign({}, clip, {
      _id:                  uid(),
      timeline_out_frames:  splitTlFrame,
      source_out_frames:    splitSrcFrame,
    });
    var right = Object.assign({}, clip, {
      _id:                  uid(),
      timeline_in_frames:   splitTlFrame,
      source_in_frames:     splitSrcFrame,
    });

    var idx = s.clips.findIndex(function (c) { return c._id === clip._id; });
    s.clips.splice(idx, 1, left, right);
    _renderClips();
    if (s.onClipsChanged) s.onClipsChanged(s.clips.slice());
  }

  // ── Area click/drop ─────────────────────────────────────────────────────────

  function _onAreaClick(e) {
    if (e.target !== e.currentTarget) return; // hit a clip, not empty space
    if (s.activeTool === 'select') {
      s.selectedId = null;
      _renderClips();
    }
  }

  function _onAreaDragOver(e) {
    if (!s.onExternalDrop) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'copy';
  }

  function _onAreaDrop(e) {
    if (!s.onExternalDrop) return;
    e.preventDefault();
    var area = e.currentTarget;
    var rect = area.getBoundingClientRect();
    var x = e.clientX - rect.left + s.container.scrollLeft;
    var track = Number(area.dataset.trackId);
    var timelineFrames = Math.max(0, pxToFrames(x));
    s.onExternalDrop({ track: track, timelineFrames: timelineFrames, dataTransfer: e.dataTransfer });
  }

  // ── Keyboard ────────────────────────────────────────────────────────────────

  function _onKeyDown(e) {
    if (!s.container) return;
    // Don't steal keys from input fields
    if (['INPUT','TEXTAREA','SELECT'].includes(document.activeElement.tagName)) return;

    // Tool shortcuts
    if (!e.ctrlKey && !e.metaKey && !e.altKey) {
      if (e.key === 'v' || e.key === 'V') { setTool('select'); return; }
      if (e.key === 'c' || e.key === 'C') { setTool('razor');  return; }
      if (e.key === 'h' || e.key === 'H') { setTool('hand');   return; }
    }

    // Delete / ripple-delete selected clip
    if ((e.key === 'Delete' || e.key === 'Backspace') && s.selectedId) {
      e.preventDefault();
      var clip = s.clips.find(function (c) { return c._id === s.selectedId; });
      if (!clip) return;

      if (e.shiftKey) {
        // Shift+Delete: extract / lift — remove clip and leave the gap
        s.clips = s.clips.filter(function (c) { return c._id !== s.selectedId; });
      } else {
        // Delete: ripple — close the gap by shifting later clips on the same track
        var clipDur  = clip.timeline_out_frames - clip.timeline_in_frames;
        var cutPoint = clip.timeline_in_frames;
        var trackId  = clip.track;
        s.clips = s.clips
          .filter(function (c) { return c._id !== s.selectedId; })
          .map(function (c) {
            if (c.track === trackId && c.timeline_in_frames >= cutPoint) {
              return Object.assign({}, c, {
                timeline_in_frames:  c.timeline_in_frames  - clipDur,
                timeline_out_frames: c.timeline_out_frames - clipDur,
              });
            }
            return c;
          });
      }

      s.selectedId = null;
      _renderClips();
      if (s.onClipsChanged) s.onClipsChanged(s.clips.slice());
    }
  }

  // ── Playhead ────────────────────────────────────────────────────────────────

  function _positionPlayhead() {
    if (!s.playheadEl) return;
    s.playheadEl.style.left = (HEADER_W + framesToPx(s.playheadFrames)) + 'px';
  }

  function setPlayhead(frames) {
    s.playheadFrames = Math.max(0, Math.round(frames));
    _positionPlayhead();
  }

  function getPlayhead() { return s.playheadFrames; }

  // ── Tool ────────────────────────────────────────────────────────────────────

  function setTool(name) {
    s.activeTool = name;
    if (s.tracksEl) {
      s.tracksEl.style.cursor =
        name === 'razor' ? 'crosshair' :
        name === 'hand'  ? 'grab'      : 'default';
    }
    _renderClips();
  }

  function getTool() { return s.activeTool; }

  // ── Scale (zoom) ─────────────────────────────────────────────────────────────

  function setScale(px) {
    s.scale = Math.min(500, Math.max(20, px));
    _renderClips();
    _renderRuler();
  }

  function getScale() { return s.scale; }

  // ── Hand tool: pan by dragging ───────────────────────────────────────────────

  function _initHandPan() {
    var dragging = false;
    var startX = 0;
    var startScroll = 0;

    s.tracksEl.addEventListener('mousedown', function (e) {
      if (s.activeTool !== 'hand') return;
      if (e.button !== 0) return;
      dragging = true;
      startX = e.clientX;
      startScroll = s.container.scrollLeft;
      s.tracksEl.style.cursor = 'grabbing';
      e.preventDefault();
    });

    document.addEventListener('mousemove', function (e) {
      if (!dragging) return;
      var dx = e.clientX - startX;
      s.container.scrollLeft = Math.max(0, startScroll - dx);
    });

    document.addEventListener('mouseup', function () {
      if (!dragging) return;
      dragging = false;
      if (s.activeTool === 'hand') s.tracksEl.style.cursor = 'grab';
    });
  }

  // ── Add clip at playhead ─────────────────────────────────────────────────────

  function addClip(asset, srcIn, srcOut, track, timelineInFrames) {
    track   = track !== undefined ? track : 0;
    srcIn   = srcIn  || 0;
    srcOut  = srcOut || (asset.duration_ms ? asset.duration_ms / 1000 : 10);

    // Use s.fps for frame conversion so clips land on the correct frame grid
    // regardless of sequence frame rate (not hardcoded to 59.94).
    var srcInFr  = Math.round(srcIn  * s.fps);
    var srcOutFr = Math.round(srcOut * s.fps);
    var tlInFr   = timelineInFrames !== undefined ? Math.max(0, Math.round(timelineInFrames)) : s.playheadFrames;
    var tlOutFr  = tlInFr + (srcOutFr - srcInFr);

    var clip = {
      _id:                  uid(),
      asset_id:             asset.id || asset.asset_id,
      display_name:         asset.display_name || asset.filename || asset.name || 'Clip',
      duration_ms:          asset.duration_ms  || null,
      streamUrl:            asset.streamUrl    || null,
      track:                track,
      timeline_in_frames:   tlInFr,
      timeline_out_frames:  tlOutFr,
      source_in_frames:     srcInFr,
      source_out_frames:    srcOutFr,
    };

    s.clips.push(clip);
    _renderClips();
    // Advance playhead to end of new clip
    setPlayhead(tlOutFr);
    if (s.onPlayheadMoved) s.onPlayheadMoved(tlOutFr);
    if (s.onClipsChanged)  s.onClipsChanged(s.clips.slice());
    return clip;
  }

  // ── Public API ───────────────────────────────────────────────────────────────

  global.Timeline = {
    init, render, setTool, getTool,
    setPlayhead, getPlayhead,
    setScale, getScale,
    addClip,
  };

})(window);
