// Player + waveform components.
// Audio is now real — backed by a single <audio> element. The
// "waveform" shape is still procedurally seeded per-clip id (cheap,
// offline, no Web Audio analysis required); only the progress sweep
// and animation live-state come from the audio element.

const { useState, useEffect, useRef, useCallback, createContext, useContext } = React;

// -------- Player context -----------------------------------------------

const PlayerCtx = createContext(null);

function parseDuration(s) {
  // "0:30" -> 30, "1:11" -> 71
  const [m, sec] = String(s).split(":").map(n => parseInt(n, 10) || 0);
  return m * 60 + sec;
}

function fmt(s) {
  if (!isFinite(s) || s < 0) s = 0;
  const m = Math.floor(s / 60);
  const r = Math.floor(s % 60);
  return m + ":" + (r < 10 ? "0" : "") + r;
}

function PlayerProvider({ children }) {
  const [clipId, setClipId]     = useState(null);
  const [playing, setPlaying]   = useState(false);
  const [progress, setProgress] = useState(0); // 0..1
  const [duration, setDuration] = useState(0); // seconds, from audio metadata

  const audioRef = useRef(null);

  // flat clip lookup
  const allClips = React.useMemo(() => {
    const m = {};
    VO.CATEGORIES.forEach(cat => cat.clips.forEach(c => {
      m[c.id] = { ...c, categoryId: cat.id, categoryTitle: cat.title };
    }));
    return m;
  }, []);

  // Lazily create the audio element on mount.
  useEffect(() => {
    const a = new Audio();
    a.preload = "metadata";
    audioRef.current = a;

    const onTime = () => {
      const d = a.duration;
      if (d && isFinite(d)) setProgress(a.currentTime / d);
    };
    const onMeta = () => {
      if (a.duration && isFinite(a.duration)) setDuration(a.duration);
    };
    const onEnd = () => {
      setPlaying(false);
      setProgress(0);
    };
    const onPlay  = () => setPlaying(true);
    const onPause = () => setPlaying(false);

    a.addEventListener("timeupdate", onTime);
    a.addEventListener("loadedmetadata", onMeta);
    a.addEventListener("durationchange", onMeta);
    a.addEventListener("ended", onEnd);
    a.addEventListener("play", onPlay);
    a.addEventListener("pause", onPause);

    return () => {
      a.removeEventListener("timeupdate", onTime);
      a.removeEventListener("loadedmetadata", onMeta);
      a.removeEventListener("durationchange", onMeta);
      a.removeEventListener("ended", onEnd);
      a.removeEventListener("play", onPlay);
      a.removeEventListener("pause", onPause);
      a.pause();
      a.src = "";
    };
  }, []);

  const play = useCallback((id) => {
    const c = allClips[id];
    const a = audioRef.current;
    if (!c || !a) return;

    if (clipId !== id) {
      // switching to a new clip — reset progress + load new src
      setClipId(id);
      setProgress(0);
      setDuration(0);
      a.src = c.src;
      a.currentTime = 0;
    }
    const p = a.play();
    if (p && typeof p.catch === "function") {
      // browsers can reject .play() before a user gesture — swallow it,
      // the click that triggered this is the gesture so it's fine in
      // practice, this is just defensive.
      p.catch(() => {});
    }
  }, [allClips, clipId]);

  const pause = useCallback(() => {
    const a = audioRef.current;
    if (a) a.pause();
  }, []);

  const toggle = useCallback((id) => {
    if (clipId === id && playing) pause();
    else play(id);
  }, [clipId, playing, play, pause]);

  const stop = useCallback(() => {
    const a = audioRef.current;
    if (a) {
      a.pause();
      a.currentTime = 0;
    }
    setPlaying(false);
    setClipId(null);
    setProgress(0);
    setDuration(0);
  }, []);

  const value = {
    clipId,
    playing,
    progress,
    duration,
    play,
    pause,
    toggle,
    stop,
    clip: clipId ? allClips[clipId] : null,
    allClips,
  };
  return <PlayerCtx.Provider value={value}>{children}</PlayerCtx.Provider>;
}

function usePlayer() { return useContext(PlayerCtx); }

// -------- Waveform -----------------------------------------------------
//
// SVG row of bars. When this clip is the active+playing one, bars get a
// CSS pulse animation phased by index. Progress is shown by recoloring
// played bars in `playedColor`.

function Waveform({ clipId, height = 56, bars = 64, color = "currentColor", playedColor, gap = 2, radius = 1.5, idle = false }) {
  const { clipId: activeId, playing, progress } = usePlayer();
  const heights = React.useMemo(() => VO.barHeights(clipId, bars), [clipId, bars]);
  const isActive = activeId === clipId;
  const live = isActive && playing;
  const playedTo = isActive ? Math.floor(progress * bars) : -1;
  const W = bars * (4 + gap) - gap;
  return (
    <svg className={"vo-wave" + (live ? " vo-wave--live" : "") + (idle ? " vo-wave--idle" : "")}
         viewBox={`0 0 ${W} ${height}`} preserveAspectRatio="none"
         style={{ width: "100%", height, display: "block", overflow: "visible" }}>
      {heights.map((h, i) => {
        const x = i * (4 + gap);
        const bh = Math.max(2, h * height);
        const y = (height - bh) / 2;
        const played = playedTo >= i;
        return (
          <rect key={i}
                x={x} y={y} width={4} height={bh} rx={radius} ry={radius}
                fill={played && playedColor ? playedColor : color}
                style={{ transformOrigin: `${x + 2}px ${height / 2}px`, animationDelay: (i * 18) + "ms" }}
                className="vo-bar" />
        );
      })}
    </svg>
  );
}

// Tiny "currently playing" oscilloscope (for headers / docks). Always
// animates when something is playing, regardless of clip id.

function NowBars({ count = 4, height = 18, color = "currentColor" }) {
  const { playing } = usePlayer();
  return (
    <span className={"vo-nowbars" + (playing ? " is-on" : "")} aria-hidden="true"
          style={{ display: "inline-flex", alignItems: "flex-end", gap: 2, height }}>
      {Array.from({ length: count }).map((_, i) => (
        <span key={i} style={{ width: 3, background: color, animationDelay: (i * 110) + "ms" }} />
      ))}
    </span>
  );
}

// -------- Play button (small / large variants) ------------------------

function PlayButton({ clipId, size = 36, color = "currentColor", bg = "transparent", border, ariaLabel }) {
  const { clipId: activeId, playing, toggle } = usePlayer();
  const isActive = activeId === clipId;
  const isPlaying = isActive && playing;
  return (
    <button onClick={(e) => { e.stopPropagation(); toggle(clipId); }}
            aria-label={ariaLabel || (isPlaying ? "Pause" : "Play")}
            className="vo-playbtn"
            style={{ width: size, height: size, borderRadius: "50%", display: "inline-flex",
                     alignItems: "center", justifyContent: "center", color, background: bg,
                     border: border || "1.5px solid currentColor", cursor: "pointer", padding: 0,
                     flex: "0 0 auto" }}>
        {isPlaying ? (
          <svg width={size * 0.42} height={size * 0.42} viewBox="0 0 12 12">
            <rect x="2" y="1.5" width="3" height="9" fill="currentColor" />
            <rect x="7" y="1.5" width="3" height="9" fill="currentColor" />
          </svg>
        ) : (
          <svg width={size * 0.42} height={size * 0.42} viewBox="0 0 12 12" style={{ marginLeft: size * 0.04 }}>
            <path d="M2 1.2 L10.5 6 L2 10.8 Z" fill="currentColor" />
          </svg>
        )}
    </button>
  );
}

window.VOPlayer = { PlayerProvider, usePlayer, Waveform, NowBars, PlayButton, fmt, parseDuration };
