// Shared UI primitives
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ---------- Mascot ----------
   Picks a dino mood gif from our mobile MVP asset set:
     - sleep      → restful / default state
     - hello      → idle greet
     - love       → great score / celebration-light
     - celebrate  → high score / streak
   `mood` overrides; otherwise inferred from `score` (sleep score 0-100).
*/
const DINO_GIFS = {
  sleep:     '/sleep/assets_dino/Dino Sleep 320x320.gif',
  hello:     '/sleep/assets_dino/Dino Hello 320x320.gif',
  love:      '/sleep/assets_dino/Dino Love 320x320.gif',
  celebrate: '/sleep/assets_dino/Dino Celebration 320x320.gif',
  knife:     '/sleep/assets_dino/Dino Knife 320x320.gif',
};

const Mascot = ({ score = 87, size = 160, mood, hideGlow = false }) => {
  const auto = score >= 90 ? 'celebrate' : score >= 75 ? 'love' : score >= 50 ? 'hello' : 'sleep';
  const key = mood || auto;
  const src = DINO_GIFS[key] || DINO_GIFS.sleep;
  return (
    <div className="mascot" style={{ '--m-size': `${size}px` }}>
      {!hideGlow && <span aria-hidden="true" />}
      <img className="dino-img" src={src} alt={`Dino mascot — ${key}`} />
    </div>
  );
};

/* ---------- Ring chart ---------- */
const Ring = ({ value = 87, max = 100, size = 180, stroke = 14, label, sub, color = 'var(--primary)', track = 'rgba(255, 255, 255, 0.08)' }) => {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const off = c * (1 - Math.min(1, value / max));
  return (
    <div style={{ position: 'relative', width: size, height: size }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={track} strokeWidth={stroke}/>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth={stroke} strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={off} style={{ filter: `drop-shadow(0 0 8px ${color})`, transition: 'stroke-dashoffset .8s' }}/>
      </svg>
      <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', textAlign: 'center' }}>
        <div>
          <div className="mono" style={{ fontSize: size * 0.32, fontWeight: 600, lineHeight: 1, letterSpacing: '-0.03em' }}>{value}</div>
          {label && <div style={{ marginTop: 6, fontSize: 13, color: 'var(--text-2)' }}>{label}</div>}
          {sub && <div style={{ marginTop: 2, fontSize: 11, color: 'var(--text-3)' }}>{sub}</div>}
        </div>
      </div>
    </div>
  );
};

/* ---------- Sparkline ---------- */
const Spark = ({ data, color = 'var(--primary)', height = 30, fill = true }) => {
  const w = 80, h = height;
  const min = Math.min(...data), max = Math.max(...data);
  const range = max - min || 1;
  const pts = data.map((v, i) => `${(i/(data.length-1))*w},${h - ((v-min)/range)*(h-2) - 1}`).join(' ');
  return (
    <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: '100%', height }}>
      {fill && (
        <polygon points={`0,${h} ${pts} ${w},${h}`} fill={color} opacity="0.12"/>
      )}
      <polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
};

/* ---------- Bar chart (vertical) ---------- */
const BarChart = ({ data, getValue, getLabel, color = 'var(--primary)', height = 110, highlight }) => {
  const max = Math.max(...data.map(getValue)) || 1;
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 6, height, paddingBottom: 18, position: 'relative' }}>
      {data.map((d, i) => {
        const v = getValue(d);
        const isHigh = highlight && highlight(d, i);
        return (
          <div key={i} style={{ flex: 1, height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
            <div style={{
              width: '100%',
              height: `${(v / max) * 100}%`,
              background: isHigh ? `linear-gradient(180deg, ${color}, var(--primary-2))` : 'rgba(255,255,255,0.06)',
              borderRadius: '4px 4px 2px 2px',
              minHeight: 2,
              boxShadow: isHigh ? '0 0 16px var(--primary-glow)' : 'none',
              transition: 'height .4s',
            }}></div>
            <div style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{getLabel(d)}</div>
          </div>
        );
      })}
    </div>
  );
};

/* ---------- Progress bar ---------- */
const ProgressBar = ({ value, max = 100, color = 'var(--primary)', height = 6 }) => {
  const pct = Math.min(100, (value / max) * 100);
  return (
    <div style={{ width: '100%', height, background: 'rgba(255,255,255,0.08)', borderRadius: 999, overflow: 'hidden' }}>
      <div style={{ width: `${pct}%`, height: '100%', background: color, borderRadius: 999, transition: 'width .5s' }}></div>
    </div>
  );
};

/* ---------- Stat card ---------- */
const StatCard = ({ label, value, unit, tag, tagTone = 'good', spark, color = 'var(--primary)' }) => (
  <div className="card" style={{ padding: 18 }}>
    <div className="metric">
      <div className="label">{label}</div>
      <div className="value tnum">{value}{unit && <span className="unit">{unit}</span>}</div>
      <div className="foot">
        {tag && <span style={{opacity: 0}} className={`tag ${tagTone}`}>{tag}</span>}
        {spark && <div style={{ width: 80 }}><Spark data={spark} color={color}/></div>}
      </div>
    </div>
  </div>
);

/* ---------- Modal ---------- */
const Modal = ({ children, onClose, width = 520 }) => {
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose?.(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ maxWidth: width }} onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
};

/* ---------- Toast ---------- */
const ToastContext = React.createContext({ push: () => {} });
const ToastProvider = ({ children }) => {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((msg, opts = {}) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(t => [...t, { id, msg, ...opts }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), opts.duration || 2500);
  }, []);
  return (
    <ToastContext.Provider value={{ push }}>
      {children}
      <div style={{ position: 'fixed', bottom: 24, right: 24, zIndex: 200, display: 'flex', flexDirection: 'column', gap: 8 }}>
        {toasts.map(t => (
          <div key={t.id} style={{
            padding: '12px 16px', borderRadius: 12,
            background: 'var(--surface-2)', border: '1px solid var(--border)',
            boxShadow: '0 12px 30px -10px rgba(0,0,0,0.4)',
            fontSize: 13, color: 'var(--text)',
            display: 'flex', alignItems: 'center', gap: 10,
            minWidth: 260,
            animation: 'pop .25s cubic-bezier(.2,.8,.2,1)',
          }}>
            <span style={{ width: 8, height: 8, borderRadius: 999, background: t.tone === 'bad' ? 'var(--bad)' : 'var(--primary)' }}></span>
            {t.msg}
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  );
};
const useToast = () => React.useContext(ToastContext);

/* ---------- Empty placeholder image ---------- */
const Placeholder = ({ label, hue = 295, height = 140 }) => {
  // Map legacy hue buckets to the brand palette.
  const c = hue <= 120 ? '#fcd34d' : hue <= 185 ? '#8fe3b8' : hue <= 255 ? '#7ad8e0' : '#b8a9ff';
  return (
    <div style={{
      height,
      borderRadius: 12,
      background: `
        repeating-linear-gradient(135deg, ${c}2e 0 8px, ${c}10 8px 16px),
        rgba(20,14,40,0.6)`,
      border: `1px solid ${c}40`,
      display: 'grid', placeItems: 'center',
      fontFamily: 'var(--font-mono)', fontSize: 11,
      color: c,
      letterSpacing: '0.04em',
      textTransform: 'uppercase',
    }}>{label}</div>
  );
};

/* ---------- App context (token, navigate) ---------- */
const AppContext = React.createContext(null);
const useApp = () => React.useContext(AppContext);

/* ---------- Sleep-sync gate ---------- */
// True when the signed-in user has no processed sleep result for today, i.e. the
// app must be opened to sync. ResultData is an empty GPTResponse (SleepScore 0) until
// the night has been synced & scored.
const needsSleepSync = () => {
  const rd = window.TODAY_SLEEP_SCORE && window.TODAY_SLEEP_SCORE.ResultData;
  return !rd || !rd.SleepScore;
};

// Store links — replace with the real listings once published.
const APP_STORE_URL = 'https://apps.apple.com/app/sleepagotchi/id6743857605';
const PLAY_STORE_URL = 'https://play.google.com/store/apps/details?id=com.sleepagotchi.soft.app';

// Full-screen overlay shown instead of the screens when the user must sync their sleep
// in the mobile app. Self-contained inline styles so it renders in both shells.
const SyncRequiredModal = () => {
  const btn = (bg) => ({
    display: 'block', width: '100%', padding: '13px 16px', borderRadius: 14,
    border: '1px solid rgba(255,255,255,0.12)', background: bg, color: '#fff',
    fontSize: 15, fontWeight: 600, textAlign: 'center', textDecoration: 'none',
    cursor: 'pointer', fontFamily: '-apple-system, system-ui, sans-serif',
  });
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 10000, background: '#0a0816',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div style={{ width: '100%', maxWidth: 380, textAlign: 'center' }}>
        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 8 }}>
          <Mascot score={0} size={120} mood="sleep"/>
        </div>
        <h2 style={{ color: '#f4f1ff', fontSize: 22, fontWeight: 700, margin: '0 0 10px' }}>
          No sleep data yet
        </h2>
        <p style={{ color: 'rgba(244,241,255,0.7)', fontSize: 15, lineHeight: 1.5, margin: '0 0 28px' }}>
          Open the Sleepagotchi app and sync your sleep to see your dashboard. Once your night
          is synced, refresh this page.
        </p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          <a href={APP_STORE_URL} target="_blank" rel="noopener noreferrer"
             style={btn('rgba(255,255,255,0.08)')}>Download on the App Store</a>
          <a href={PLAY_STORE_URL} target="_blank" rel="noopener noreferrer"
             style={btn('rgba(255,255,255,0.08)')}>Get it on Google Play</a>
          <button onClick={() => window.location.reload()}
             style={btn('linear-gradient(135deg, var(--primary, #a78bfa), var(--accent-cyan, #7ad8e0))')}>
            Refresh page
          </button>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, {
  Mascot, Ring, Spark, BarChart, ProgressBar, StatCard, Modal,
  ToastProvider, useToast, Placeholder, AppContext, useApp,
  needsSleepSync, SyncRequiredModal,
});
