/**
 * Playback position, published outside React.
 *
 * The lyrics pane no longer lives in the player's subtree — it renders in a panel of the /music
 * workspace — so the position has to cross the tree. It cannot cross as state: the engine reports a new
 * position every animation frame, and a shared state channel would re-render every consumer 60× a
 * second. Instead the host pushes into this module and subscribers decide for themselves what is worth
 * a render (the lyrics pane only re-renders when the ACTIVE LINE changes, roughly once a line).
 *
 * Seek travels the other way for the same reason: the engine is the host's, but a click on a lyric line
 * has to reach it.
 */
let position = 0;
let duration = 0;
const subscribers = new Set<(sec: number, dur: number) => void>();
let seekFn: ((sec: number) => void) | null = null;

export const publishPlayerTime = (sec: number, dur: number): void => {
  position = sec;
  duration = dur;
  for (const fn of subscribers) fn(sec, dur);
};

/** Latest values, for a subscriber that mounts mid-track. */
export const getPlayerTime = (): number => position;
export const getPlayerDuration = (): number => duration;

export const subscribePlayerTime = (fn: (sec: number, dur: number) => void): (() => void) => {
  subscribers.add(fn);
  return () => {
    subscribers.delete(fn);
  };
};

export const registerPlayerSeek = (fn: (sec: number) => void): (() => void) => {
  seekFn = fn;
  return () => {
    if (seekFn === fn) seekFn = null;
  };
};

export const seekPlayer = (sec: number): void => seekFn?.(sec);
