// Shared bits for the Workshop SPA (loaded by index.html through Babel standalone,
// in order: common.jsx, hoot.jsx, quiz.jsx, then the activities, then the app).
const { useState, useEffect, useCallback, useRef } = React;

// ─── API HELPER ─────────────────────────────
// Never throws: a network error or a non-JSON reply (a proxy error page) comes
// back as { error } so a screen can show a friendly message instead of going blank.
async function api(path, opts = {}) {
  let res;
  try {
    res = await fetch(path, {
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      ...opts,
      body: opts.body ? JSON.stringify(opts.body) : undefined
    });
  } catch (e) {
    return { error: 'offline' };
  }
  let data = null;
  try { data = await res.json(); } catch (e) { data = null; }
  if (res.status === 401 && data && data.redirect) {
    window.location.href = data.redirect;
    return null;
  }
  if (!res.ok) return { error: (data && data.error) || `HTTP ${res.status}`, status: res.status };
  return data;
}

// ─── CONFETTI ───────────────────────────────
function Confetti({ count = 50 }) {
  const pieces = useRef(null);
  if (!pieces.current) {
    const colors = ['#FF6B6B','#FFD700','#4ECDC4','#9B59B6','#2ECC71','#FF8E8E','#3498DB'];
    pieces.current = Array.from({ length: count }).map(() => ({
      left: Math.random() * 100 + '%',
      background: colors[Math.floor(Math.random() * colors.length)],
      borderRadius: Math.random() > 0.5 ? '50%' : '2px',
      width: Math.random() * 8 + 6 + 'px',
      height: Math.random() * 8 + 6 + 'px',
      '--fall-dur': (Math.random() * 2 + 1.5) + 's',
      animationDelay: Math.random() * 0.5 + 's'
    }));
  }
  return (
    <div className="confetti-container">
      {pieces.current.map((style, i) => <div key={i} className="confetti-piece" style={style} />)}
    </div>
  );
}

// ─── BADGES ────────────────────────────────
// Badge art lands at /images/badges/<slug>.webp from the art night; until then
// (or if a file is missing) the badge's emoji shows.
function BadgeArt({ badge, size = 64 }) {
  const [broken, setBroken] = useState(false);
  if (broken || !badge.slug) return <span className="emoji" style={{ fontSize: size * 0.8 }}>{badge.emoji}</span>;
  return <img src={`/images/badges/${badge.slug}.webp`} alt="" width={size} height={size}
    style={{ borderRadius: '50%' }} onError={() => setBroken(true)} />;
}

// Shows every badge earned, one after another (the old popup showed only the
// first, and a first quiz usually earns two or three at once).
function BadgeQueue({ badges, onDone }) {
  const [i, setI] = useState(0);
  useEffect(() => { setI(0); }, [badges]);
  if (!badges || i >= badges.length) return null;
  const badge = badges[i];
  const next = () => {
    if (i + 1 >= badges.length) { setI(badges.length); onDone && onDone(); }
    else setI(i + 1);
  };
  return (
    <div className="badge-popup-overlay" onClick={next}>
      <div className="badge-popup" key={badge.slug} onClick={e => e.stopPropagation()}>
        <div className="badge-new-label">New badge!</div>
        <BadgeArt badge={badge} size={96} />
        <h3>{badge.name}</h3>
        <div className="desc">{badge.description}</div>
        <button className="btn-primary" onClick={next}>
          {i + 1 < badges.length ? `Awesome! (${badges.length - i - 1} more)` : 'Awesome!'}
        </button>
      </div>
      <Confetti count={30} />
    </div>
  );
}

// ─── PROGRESS RING ──────────────────────────
function ProgressRing({ percent, color }) {
  const circumference = 2 * Math.PI * 14;
  const offset = circumference - (percent / 100) * circumference;
  return (
    <svg className="progress-ring" viewBox="0 0 36 36">
      <circle className="bg" r="14" cx="18" cy="18" />
      <circle className="fg" r="14" cx="18" cy="18"
        stroke={color} strokeDasharray={circumference}
        strokeDashoffset={offset}
        style={{ transform: 'rotate(-90deg)', transformOrigin: 'center' }} />
    </svg>
  );
}

// ─── ACTIVITY ICON ──────────────────────────
// /images/icons/<slug>.webp once the art night's picks land; the emoji until then.
function ActivityIcon({ activity, size = 56 }) {
  const [broken, setBroken] = useState(false);
  if (broken) return <div className="icon">{activity.icon}</div>;
  return <img className="activity-img" src={`/images/icons/${activity.slug}.webp`} alt="" width={size} height={size}
    onError={() => setBroken(true)} />;
}

// ─── STARS for a score ──────────────────────
function starsFor(score, total) {
  if (!total) return 0;
  const pct = score / total;
  return pct === 1 ? 3 : pct >= 0.7 ? 2 : pct > 0 ? 1 : 0;
}
function Stars({ score, total, size = '1rem' }) {
  const n = starsFor(score, total);
  return <span className="stars" style={{ fontSize: size }} aria-label={`${n} of 3 stars`}>
    {[0, 1, 2].map(i => <span key={i} className={i < n ? 'star-on' : 'star-off'}>★</span>)}
  </span>;
}

// ─── ERROR CARD ────────────────────────────
function OopsCard({ message, onRetry }) {
  return (
    <div className="oops-card">
      <div style={{ fontSize: '2.5rem' }}>🦉</div>
      <h3>Hmm, something got stuck.</h3>
      <p>{message || 'The Workshop could not load. Let’s try again!'}</p>
      {onRetry && <button className="btn-primary" onClick={onRetry}>Try again</button>}
    </div>
  );
}
