// Audio engine — single shared instance.
// - Real audio when track.src is set; otherwise simulated playback that ticks
//   currentTime against the track's parsed duration.
// - Only one track plays at a time.
// - Subscribers get notified on play/pause, time updates, track changes.

const AudioEngine = (() => {
  const listeners = new Set();
  let state = {
    trackId: null,
    track: null,
    isPlaying: false,
    currentTime: 0, // seconds
    duration: 0,    // seconds
    isReal: false,
  };

  const audio = new Audio();
  audio.preload = "metadata";

  let simTimer = null;
  let lastTick = 0;

  const parseDuration = (str) => {
    if (!str) return 0;
    const [m, s] = str.split(":").map(Number);
    return (m || 0) * 60 + (s || 0);
  };

  const emit = () => {
    for (const fn of listeners) fn(state);
  };
  const set = (patch) => {
    state = { ...state, ...patch };
    emit();
  };

  audio.addEventListener("timeupdate", () => {
    if (state.isReal) set({ currentTime: audio.currentTime });
  });
  audio.addEventListener("loadedmetadata", () => {
    if (state.isReal) set({ duration: audio.duration });
  });
  audio.addEventListener("ended", () => {
    set({ isPlaying: false, currentTime: 0 });
  });

  const stopSim = () => {
    if (simTimer) {
      cancelAnimationFrame(simTimer);
      simTimer = null;
    }
  };
  const startSim = () => {
    stopSim();
    lastTick = performance.now();
    const step = (now) => {
      const dt = (now - lastTick) / 1000;
      lastTick = now;
      let next = state.currentTime + dt;
      if (next >= state.duration) {
        set({ isPlaying: false, currentTime: 0 });
        stopSim();
        return;
      }
      set({ currentTime: next });
      simTimer = requestAnimationFrame(step);
    };
    simTimer = requestAnimationFrame(step);
  };

  const playTrack = (track) => {
    // Toggle off if same track currently playing.
    if (state.trackId === track.id && state.isPlaying) {
      pause();
      return;
    }
    // Resume same track if paused.
    if (state.trackId === track.id && !state.isPlaying) {
      resume();
      return;
    }

    stopSim();
    audio.pause();
    const isReal = !!track.src;
    set({
      trackId: track.id,
      track,
      isPlaying: true,
      currentTime: 0,
      duration: parseDuration(track.duration),
      isReal,
    });
    if (isReal) {
      audio.src = track.src;
      audio.currentTime = 0;
      audio.play().catch(() => {
        // Autoplay can fail silently — fall back to simulation so UI stays alive.
        set({ isReal: false });
        startSim();
      });
    } else {
      startSim();
    }
  };

  const pause = () => {
    if (state.isReal) audio.pause();
    else stopSim();
    set({ isPlaying: false });
  };

  const resume = () => {
    if (!state.track) return;
    if (state.isReal) {
      audio.play().catch(() => {});
    } else {
      startSim();
    }
    set({ isPlaying: true });
  };

  const seek = (seconds) => {
    const clamped = Math.max(0, Math.min(state.duration, seconds));
    if (state.isReal) audio.currentTime = clamped;
    set({ currentTime: clamped });
    if (state.isPlaying && !state.isReal) {
      lastTick = performance.now();
    }
  };

  const subscribe = (fn) => {
    listeners.add(fn);
    fn(state);
    return () => listeners.delete(fn);
  };

  return { playTrack, pause, resume, seek, subscribe };
})();

// React hook to subscribe to engine state.
function useAudioState() {
  const [s, setS] = React.useState({
    trackId: null, track: null, isPlaying: false,
    currentTime: 0, duration: 0, isReal: false,
  });
  React.useEffect(() => AudioEngine.subscribe(setS), []);
  return s;
}

const formatTime = (sec) => {
  if (!isFinite(sec) || sec < 0) sec = 0;
  const m = Math.floor(sec / 60);
  const s = Math.floor(sec % 60);
  return `${m}:${String(s).padStart(2, "0")}`;
};

window.AudioEngine = AudioEngine;
window.useAudioState = useAudioState;
window.formatTime = formatTime;
