// Professor Hoot: the picture, the lines, and the voice player.
//
// Voice: plays /audio/hoot/<key>.m4a when that file exists and is silent
// otherwise. The server lists the clips that exist (GET /api/hoot), so a
// missing clip never even makes a request, and a "Read it to me" button only
// appears where there is something to hear. Every line and question that
// should be spoken is in content/hoot/voice-manifest.json (key -> text), built
// by scripts/build-hoot-manifest.js; Stephen records/generates the clips from it.
//
// Keys: Hoot lines are "hoot.<pool>.<n>" (pools in content/hoot/lines.json);
// quiz questions are "q.<qkey>" (questions.qkey, e.g. q.space-explorer.planet-spotter.03).

const HootVoice = (() => {
  let audio = new Set();
  let lines = {};
  let loaded = null;
  let current = null;
  let ready = false;
  const listeners = new Set();
  function load() {
    if (!loaded) {
      loaded = api('/api/hoot').then(d => {
        if (d && !d.error) {
          audio = new Set(d.audio || []);
          lines = d.lines || {};
        }
        ready = true;
        listeners.forEach(fn => fn());
      });
    }
    return loaded;
  }
  function has(key) { return !!key && audio.has(key); }
  function stop() {
    if (current) { try { current.pause(); } catch (e) {} current = null; }
  }
  // Must be called from a tap (iPad Safari only plays audio from a user gesture).
  function play(key) {
    if (!has(key)) return false;
    stop();
    current = new Audio(`/audio/hoot/${encodeURIComponent(key)}.m4a`);
    current.play().catch(() => {});
    return true;
  }
  // A line from a pool: { key, text }. Random unless n is given.
  function line(pool, n) {
    const list = lines[pool];
    if (!list || !list.length) return null;
    const i = n === undefined ? Math.floor(Math.random() * list.length) : n % list.length;
    return { key: `hoot.${pool}.${i + 1}`, text: list[i] };
  }
  function subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); }
  return { load, has, play, stop, line, subscribe, isReady: () => ready };
})();

function useHootVoice() {
  const [, bump] = useState(0);
  useEffect(() => {
    HootVoice.load();
    return HootVoice.subscribe(() => bump(n => n + 1));
  }, []);
  return HootVoice;
}

// Hoot's picture for a pose (/images/hoot/<pose>.webp), then the main pose
// (/images/hoot/hoot.webp), then the owl emoji, whichever exists first.
function Hoot({ pose = 'hoot', size = 120, className = '' }) {
  const order = pose === 'hoot' ? ['hoot'] : [pose, 'hoot'];
  const [step, setStep] = useState(0);
  useEffect(() => { setStep(0); }, [pose]);
  if (step >= order.length) {
    return <div className={`hoot-emoji ${className}`} style={{ fontSize: size * 0.7, width: size, height: size }} aria-hidden="true">🦉</div>;
  }
  return <img className={`hoot-img ${className}`} src={`/images/hoot/${order[step]}.webp`} alt="Professor Hoot"
    style={{ height: size, width: 'auto' }} onError={() => setStep(s => s + 1)} />;
}

// A speaker button, shown only when there is a clip for this key.
function SpeakButton({ voiceKey, label = 'Read it to me', dark = false }) {
  const voice = useHootVoice();
  if (!voice.has(voiceKey)) return null;
  return (
    <button className={`speak-btn ${dark ? 'dark' : ''}`} onClick={e => { e.stopPropagation(); voice.play(voiceKey); }}
      aria-label={label}>
      🔊 <span>{label}</span>
    </button>
  );
}

// Hoot with a speech bubble. Pass a pool (a random line from it) or a fixed
// text, with its voiceKey if it has one (e.g. hoot.intro.<slug>).
function HootSays({ pool, n, text, voiceKey, pose = 'hello', size = 110, children, dark = false }) {
  const voice = useHootVoice();
  const [pick, setPick] = useState(null);
  useEffect(() => { if (pool) setPick(voice.line(pool, n)); }, [pool, n, voice.isReady()]);
  const shown = text || (pick && pick.text) || '';
  return (
    <div className={`hoot-says ${dark ? 'dark' : ''}`}>
      <Hoot pose={pose} size={size} />
      <div className="hoot-bubble">
        {children}
        {shown && <p>{shown}</p>}
        {(pick || voiceKey) && <SpeakButton voiceKey={pick ? pick.key : voiceKey} label="Hear Hoot" dark={dark} />}
      </div>
    </div>
  );
}
