/* Shared timeline player for EMDS Workbench tutorial demos.
 * A demo HTML file builds its mock UI, defines a timeline array, and calls
 * EmdsDemo.run({ duration, timeline, stage, captionEl, cursor }).
 *
 * Timeline entries:
 *   { t: <ms from start>, caption: "<text>", action: () => {...} }
 *
 * The player handles: caption swapping, action firing on schedule,
 * Play / Pause / Restart buttons, progress bar, and elapsed-time display.
 */
(function (global) {
  const fmt = (ms) => {
    const s = Math.max(0, Math.floor(ms / 1000));
    const m = Math.floor(s / 60);
    const r = s % 60;
    return `${m}:${r.toString().padStart(2, "0")}`;
  };

  function run(config) {
    const {
      duration,
      timeline,
      captionEl,
      progressFill,
      timeEl,
      playBtn,
      restartBtn,
    } = config;

    const sorted = [...timeline].sort((a, b) => a.t - b.t);
    let lastFiredIndex = -1;
    let startWallTime = null;
    let pausedElapsed = 0;
    let playing = false;
    let rafId = null;
    let lastCaption = "";

    function applyCaptionForElapsed(elapsed) {
      let current = "";
      for (let i = 0; i < sorted.length; i++) {
        if (sorted[i].t <= elapsed) current = sorted[i].caption ?? current;
        else break;
      }
      if (current !== lastCaption) {
        lastCaption = current;
        if (captionEl) {
          captionEl.style.opacity = "0";
          setTimeout(() => {
            captionEl.textContent = current;
            captionEl.style.opacity = "1";
          }, 180);
        }
      }
    }

    function fireActionsThrough(elapsed) {
      for (let i = lastFiredIndex + 1; i < sorted.length; i++) {
        if (sorted[i].t <= elapsed) {
          try { if (typeof sorted[i].action === "function") sorted[i].action(); }
          catch (e) { console.error("demo action failed", e); }
          lastFiredIndex = i;
        } else break;
      }
    }

    function resetVisual() {
      lastFiredIndex = -1;
      lastCaption = "";
      if (captionEl) captionEl.textContent = "";
      if (progressFill) progressFill.style.width = "0%";
      if (timeEl) timeEl.textContent = `0:00 / ${fmt(duration)}`;
      if (typeof config.onReset === "function") {
        try { config.onReset(); } catch (e) { console.error(e); }
      }
    }

    function tick() {
      if (!playing) return;
      const elapsed = pausedElapsed + (performance.now() - startWallTime);
      const clamped = Math.min(elapsed, duration);

      if (progressFill) {
        progressFill.style.width = `${(clamped / duration) * 100}%`;
      }
      if (timeEl) {
        timeEl.textContent = `${fmt(clamped)} / ${fmt(duration)}`;
      }

      fireActionsThrough(clamped);
      applyCaptionForElapsed(clamped);

      if (clamped >= duration) {
        pause();
        return;
      }
      rafId = requestAnimationFrame(tick);
    }

    function play() {
      if (playing) return;
      // If we're at or past the end, restart.
      if (pausedElapsed >= duration) {
        restart(true);
        return;
      }
      playing = true;
      startWallTime = performance.now();
      if (playBtn) playBtn.textContent = "Pause";
      rafId = requestAnimationFrame(tick);
    }

    function pause() {
      if (!playing) return;
      playing = false;
      pausedElapsed += performance.now() - startWallTime;
      if (playBtn) playBtn.textContent = "Play";
      if (rafId) cancelAnimationFrame(rafId);
    }

    function restart(autoplay) {
      if (rafId) cancelAnimationFrame(rafId);
      playing = false;
      pausedElapsed = 0;
      resetVisual();
      if (autoplay) {
        play();
      } else {
        if (playBtn) playBtn.textContent = "Play";
      }
    }

    if (playBtn) {
      playBtn.addEventListener("click", () => (playing ? pause() : play()));
    }
    if (restartBtn) {
      restartBtn.addEventListener("click", () => restart(true));
    }

    resetVisual();
    // Auto-start playback.
    play();

    return { play, pause, restart };
  }

  global.EmdsDemo = { run };
})(window);
