// App.jsx — main app state, filters, grid, Tweaks wiring.

const { useState, useEffect, useMemo } = React;

const LS_KEY = 'stars-we-have-seen-v1';

function loadState() {
  try { return JSON.parse(localStorage.getItem(LS_KEY)) || {}; } catch { return {}; }
}
function saveState(s) { localStorage.setItem(LS_KEY, JSON.stringify(s)); }

function App() {
  const [kidName, setKidName] = useState(() => loadState().kidName || 'Nova');
  const [logs, setLogs] = useState(() => loadState().logs || {});
  const [cat, setCat] = useState('All');
  const [query, setQuery] = useState('');
  const [showOnly, setShowOnly] = useState('all');
  const [sort, setSort] = useState('difficulty');
  const [active, setActive] = useState(null);
  const [view, setView] = useState('catalog');

  const [tweaks, setTweaks] = useState(() => window.TWEAKS);
  const [tweakOpen, setTweakOpen] = useState(false);
  const [certBadge, setCertBadge] = useState(null);
  const [earnedAt, setEarnedAt] = useState(() => loadState().earnedAt || {});
  const [auth, setAuth] = useState(() => window.SkyAuth?.state() || { signedIn: false });
  const [synced, setSynced] = useState(false);

  // Subscribe to auth changes; on sign-in, pull data from Drive.
  useEffect(() => {
    if (!window.SkyAuth) return;
    return window.SkyAuth.subscribe(async (s) => {
      setAuth(s);
      if (s.signedIn && !synced) {
        const data = await window.SkyAuth.loadData();
        if (data && (data.logs || data.kidName || data.earnedAt)) {
          if (data.kidName) setKidName(data.kidName);
          if (data.logs) setLogs(data.logs);
          if (data.earnedAt) setEarnedAt(data.earnedAt);
        }
        setSynced(true);
      }
      if (!s.signedIn) setSynced(false);
    });
  }, [synced]);

  // Persist to localStorage + (debounced) to Drive.
  useEffect(() => {
    saveState({ kidName, logs, earnedAt });
    if (!auth.signedIn || !synced) return;
    const t = setTimeout(() => {
      window.SkyAuth.saveData({ kidName, logs, earnedAt, updatedAt: new Date().toISOString() });
    }, 800);
    return () => clearTimeout(t);
  }, [kidName, logs, earnedAt, auth.signedIn, synced]);

  useEffect(() => {
    const handler = (e) => {
      if (!e.data || typeof e.data !== 'object') return;
      if (e.data.type === '__activate_edit_mode') setTweakOpen(true);
      if (e.data.type === '__deactivate_edit_mode') setTweakOpen(false);
    };
    window.addEventListener('message', handler);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', handler);
  }, []);

  const applyTweak = (key, val) => {
    setTweaks(t => ({ ...t, [key]: val }));
    window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: val } }, '*');
  };

  // Apply tweak CSS vars
  useEffect(() => {
    const root = document.documentElement;
    root.dataset.theme = tweaks.theme;
    root.dataset.accent = tweaks.accent;
    root.dataset.density = tweaks.density;
    if (tweaks.motion === 'reduced') root.dataset.motion = 'reduced';
    else delete root.dataset.motion;
  }, [tweaks]);

  // Live moon phase for the Hero + anywhere else that wants it.
  const [tick, setTick] = useState(0);
  useEffect(() => {
    const t = setInterval(() => setTick(n => n + 1), 60000);
    return () => clearInterval(t);
  }, []);
  const moonNow = useMemo(() => window.moonPhase(new Date()), [tick]);

  const filtered = useMemo(() => {
    let list = window.CATALOG.slice();
    if (cat !== 'All') list = list.filter(o => o.cat === cat);
    if (query.trim()) {
      const q = query.toLowerCase();
      list = list.filter(o => o.name.toLowerCase().includes(q) || o.tags.some(t => t.toLowerCase().includes(q)));
    }
    if (showOnly === 'seen') list = list.filter(o => logs[o.id]);
    if (showOnly === 'unseen') list = list.filter(o => !logs[o.id]);
    if (sort === 'difficulty') list.sort((a, b) => a.difficulty - b.difficulty);
    if (sort === 'name') list.sort((a, b) => a.name.localeCompare(b.name));
    if (sort === 'category') list.sort((a, b) => a.cat.localeCompare(b.cat) || a.difficulty - b.difficulty);
    if (sort === 'recent') list.sort((a, b) => {
      const la = logs[a.id]?.date || ''; const lb = logs[b.id]?.date || '';
      return lb.localeCompare(la);
    });
    return list;
  }, [cat, query, showOnly, sort, logs]);

  const seen = Object.keys(logs).length;
  const total = window.CATALOG.length;

  // Next-up target: easiest unseen object (breaks ties with brightest).
  const nextUp = useMemo(() => {
    const unseen = window.CATALOG.filter(o => !logs[o.id]);
    if (!unseen.length) return null;
    unseen.sort((a, b) =>
      a.difficulty - b.difficulty ||
      (a.mag ?? 99) - (b.mag ?? 99)
    );
    return unseen[0];
  }, [logs]);

  // Category counts
  const catCounts = useMemo(() => {
    const out = { All: { seen: 0, total: window.CATALOG.length } };
    for (const o of window.CATALOG) {
      out[o.cat] = out[o.cat] || { seen: 0, total: 0 };
      out[o.cat].total++;
      if (logs[o.id]) {
        out[o.cat].seen++;
        out.All.seen++;
      }
    }
    return out;
  }, [logs]);

  // Badges — icons use `currentColor`; the .medal class handles fill per state.
  const badges = useMemo(() => [
    {
      id: 'first-light', name: 'First light', desc: 'LOG YOUR 1ST OBJECT',
      earned: seen >= 1,
      icon: <path d="M17 9 v16 M9 17 h16" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/>
    },
    {
      id: 'five-alive', name: 'Take five', desc: 'LOG 5 OBJECTS',
      earned: seen >= 5,
      icon: <text x="17" y="21" textAnchor="middle" fontSize="14" fontFamily="'Fraunces', serif" fontWeight="700" fill="currentColor">5</text>
    },
    {
      id: 'planet-hop', name: 'Planet hopper', desc: '3 PLANETS',
      earned: (catCounts.Planet?.seen || 0) >= 3,
      icon: <g fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="17" cy="17" r="3"/><ellipse cx="17" cy="17" rx="8" ry="2.5"/></g>
    },
    {
      id: 'deep-sky', name: 'Deep sky', desc: 'LOG A GALAXY',
      earned: Object.keys(logs).some(id => window.CATALOG.find(o => o.id === id)?.cat === 'Galaxy'),
      icon: <g stroke="currentColor" strokeWidth="1.2" fill="none"><ellipse cx="17" cy="17" rx="9" ry="3" transform="rotate(25 17 17)"/><circle cx="17" cy="17" r="1.6" fill="currentColor"/></g>
    },
    {
      id: 'messier', name: 'Messier hunter', desc: 'LOG 3 MESSIER',
      earned: Object.keys(logs).filter(id => window.CATALOG.find(o => o.id === id)?.tags.includes('Messier')).length >= 3,
      icon: <text x="17" y="21" textAnchor="middle" fontSize="13" fontFamily="'Fraunces', serif" fontWeight="700" fill="currentColor">M</text>
    },
    {
      id: 'all-cats', name: 'Whole sky', desc: 'ONE OF EACH KIND',
      earned: ['Moon', 'Planet', 'Star', 'Constellation', 'Nebula', 'Cluster', 'Galaxy']
        .every(c => (catCounts[c]?.seen || 0) >= 1),
      icon: <g fill="currentColor">
        <circle cx="12" cy="17" r="1.5"/><circle cx="17" cy="12" r="1.5"/><circle cx="22" cy="17" r="1.5"/><circle cx="17" cy="22" r="1.5"/><circle cx="17" cy="17" r="1.5"/>
      </g>
    },
    {
      id: 'cartographer', name: 'Cartographer', desc: 'LOG EVERY OBJECT',
      earned: seen >= total && total > 0,
      icon: <g fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round">
          <path d="M10 11 L17 8 L24 11 L24 24 L17 21 L10 24 Z"/>
          <line x1="17" y1="8" x2="17" y2="21"/>
          <circle cx="21" cy="14" r="1.2" fill="currentColor"/>
        </g>
    },
  ], [seen, total, catCounts, logs]);

  // Track the first-earned date per badge.
  useEffect(() => {
    const today = new Date().toISOString().slice(0, 10);
    setEarnedAt(prev => {
      let changed = false;
      const next = { ...prev };
      for (const b of badges) {
        if (b.earned && !next[b.id]) { next[b.id] = today; changed = true; }
      }
      return changed ? next : prev;
    });
  }, [badges]);

  const [celebrate, setCelebrate] = useState(null); // { id }

  const logObject = (obj, entry, wasNew) => {
    setLogs(l => ({ ...l, [obj.id]: entry }));
    if (wasNew) {
      const id = Date.now();
      setCelebrate({ id });
      setTimeout(() => setCelebrate(c => (c && c.id === id ? null : c)), 1200);
    }
  };
  const unlogObject = (obj) => {
    setLogs(l => { const n = { ...l }; delete n[obj.id]; return n; });
    setActive(null);
  };

  const toggleTheme = () => applyTweak('theme', tweaks.theme === 'dark' ? 'light' : 'dark');

  return (
    <div className="app">
      <StarField />
      <Header
        kidName={kidName}
        onEditName={setKidName}
        total={total}
        seen={seen}
        theme={tweaks.theme}
        onToggleTheme={toggleTheme}
        auth={auth}
        onSignIn={() => window.SkyAuth.signIn()}
        onSignOut={() => window.SkyAuth.signOut()}
      />

      <BadgeRail badges={badges} onOpen={setCertBadge} />

      {view === 'catalog' && (
        <Hero
          kidName={kidName}
          seen={seen}
          total={total}
          nextUp={nextUp}
          onPickNext={setActive}
          earnedCount={badges.filter(b => b.earned).length}
          moon={moonNow}
        />
      )}

      <div className="viewswitch" role="tablist">
        <button className="viewswitch__btn" data-active={view === 'catalog'} onClick={() => setView('catalog')}>
          <svg width="14" height="14" viewBox="0 0 14 14"><rect x="1" y="1" width="5" height="5" fill="none" stroke="currentColor" strokeWidth="1.2"/><rect x="8" y="1" width="5" height="5" fill="none" stroke="currentColor" strokeWidth="1.2"/><rect x="1" y="8" width="5" height="5" fill="none" stroke="currentColor" strokeWidth="1.2"/><rect x="8" y="8" width="5" height="5" fill="none" stroke="currentColor" strokeWidth="1.2"/></svg>
          Catalog
        </button>
        <button className="viewswitch__btn" data-active={view === 'map'} onClick={() => setView('map')}>
          <svg width="14" height="14" viewBox="0 0 14 14"><circle cx="7" cy="7" r="6" fill="none" stroke="currentColor" strokeWidth="1.2"/><circle cx="4" cy="4" r="0.8" fill="currentColor"/><circle cx="10" cy="6" r="0.8" fill="currentColor"/><circle cx="6" cy="10" r="0.8" fill="currentColor"/></svg>
          Sky Map
        </button>
      </div>

      {view === 'catalog' && (<>
      <div className="toolbar">
        <div className="toolbar__cats">
          {window.CATEGORIES.map(c => {
            const cc = catCounts[c] || { seen: 0, total: 0 };
            return (
              <button
                key={c}
                className="cat-pill"
                data-active={cat === c}
                onClick={() => setCat(c)}
              >
                <span>{c}</span>
                <span className="cat-pill__count mono">{cc.seen}/{cc.total}</span>
              </button>
            );
          })}
        </div>
        <div className="toolbar__right">
          <div className="search">
            <svg width="14" height="14" viewBox="0 0 14 14"><circle cx="6" cy="6" r="4" fill="none" stroke="currentColor" strokeWidth="1.3"/><line x1="9" y1="9" x2="12.5" y2="12.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>
            <input placeholder="Search the sky…" value={query} onChange={e => setQuery(e.target.value)} />
          </div>
          <select value={showOnly} onChange={e => setShowOnly(e.target.value)}>
            <option value="all">All objects</option>
            <option value="seen">Only seen</option>
            <option value="unseen">Not yet seen</option>
          </select>
          <select value={sort} onChange={e => setSort(e.target.value)}>
            <option value="difficulty">By difficulty</option>
            <option value="name">By name</option>
            <option value="category">By category</option>
            <option value="recent">Recently seen</option>
          </select>
        </div>
      </div>

      <main className="grid">
        {filtered.length === 0 ? (
          <div className="empty">
            <div className="empty__icon">
              <svg width="40" height="40" viewBox="0 0 40 40"><circle cx="20" cy="20" r="12" fill="none" stroke="var(--ink-500)" strokeWidth="1" strokeDasharray="3 3"/></svg>
            </div>
            <div className="empty__title">Nothing to show</div>
            <div className="empty__sub">Try a different filter or clear the search.</div>
          </div>
        ) : filtered.map((o, i) => (
          <SkyCard key={o.id} obj={o} log={logs[o.id]} onClick={() => setActive(o)} index={Math.min(i, 14)} />
        ))}
      </main>

      </>)}

      {view === 'map' && (<>
        <MoonWidget
          seenIds={new Set(Object.keys(logs))}
          onPickMoonObject={setActive}
        />
        <Planisphere logs={logs} onPick={setActive} />
      </>)}

      <footer className="foot mono">
        <span>STARS WE HAVE SEEN · FIELD LOG · {new Date().getFullYear()}</span>
        <span className="foot__sep" aria-hidden="true">·</span>
        <a
          className="foot__tiktok"
          href="https://www.tiktok.com/@gouniversego"
          target="_blank"
          rel="noopener noreferrer"
          aria-label="Follow GoUniverseGo on TikTok"
        >
          <svg width="12" height="12" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor">
            <path d="M19.6 5.8a5.7 5.7 0 0 1-3.2-1.5A5.6 5.6 0 0 1 14.8 1h-3.1v13.4a2.7 2.7 0 0 1-5-1.4 2.7 2.7 0 0 1 3.5-2.6V7.2a5.8 5.8 0 0 0-6.6 5.8 5.8 5.8 0 0 0 10 4.1 5.7 5.7 0 0 0 1.7-4.1V8.9a8.7 8.7 0 0 0 4.3 1.2V7a5.7 5.7 0 0 1 0-1.2Z"/>
          </svg>
          <span>@GOUNIVERSEGO</span>
        </a>
      </footer>

      {active && (
        <DetailSheet
          obj={active}
          log={logs[active.id]}
          onClose={() => setActive(null)}
          onLog={(entry) => {
            const wasNew = !logs[active.id];
            logObject(active, entry, wasNew);
            setActive(null);
          }}
          onUnlog={() => unlogObject(active)}
        />
      )}

      {celebrate && <Celebration key={celebrate.id} />}

      {certBadge && (
        <Certificate
          badge={certBadge}
          kidName={kidName}
          earnedDate={earnedAt[certBadge.id]}
          catalog={window.CATALOG}
          logs={logs}
          onClose={() => setCertBadge(null)}
        />
      )}

      {tweakOpen && (
        <TweaksPanel
          tweaks={tweaks}
          onChange={applyTweak}
          kidName={kidName}
          onName={setKidName}
          onReset={() => { if (confirm('Clear all sightings?')) setLogs({}); }}
          onClose={() => setTweakOpen(false)}
        />
      )}
    </div>
  );
}

function Celebration() {
  // Amber ripple + 12 gold-star burst, centered on viewport.
  const stars = React.useMemo(() => {
    const out = [];
    for (let i = 0; i < 12; i++) {
      const angle = (i / 12) * Math.PI * 2 + Math.random() * 0.3;
      const dist = 140 + Math.random() * 120;
      out.push({
        dx: Math.cos(angle) * dist,
        dy: Math.sin(angle) * dist - 20,
        delay: Math.random() * 120,
      });
    }
    return out;
  }, []);
  return (
    <div className="celebrate" aria-hidden="true">
      <div className="celebrate__ripple" />
      {stars.map((s, i) => (
        <svg
          key={i}
          className="celebrate__star"
          viewBox="0 0 14 14"
          style={{
            '--dx': `${s.dx}px`,
            '--dy': `${s.dy}px`,
            animationDelay: `${s.delay}ms`,
          }}
        >
          <path d="M7 0.8l1.8 4 4.4.4-3.3 3 1 4.4L7 10.3l-3.9 2.3 1-4.4-3.3-3 4.4-.4z" fill="currentColor" />
        </svg>
      ))}
    </div>
  );
}

function StarField() {
  // Decorative twinkling background — absolutely-positioned dots, not SVG.
  const stars = useMemo(() => {
    const out = [];
    for (let i = 0; i < 80; i++) {
      out.push({
        x: Math.random() * 100,
        y: Math.random() * 100,
        r: Math.random() * 1.6 + 0.5,
        d: Math.random() * 4 + 2,
        t: Math.random() * 4,
        o: Math.random() * 0.5 + 0.3,
      });
    }
    return out;
  }, []);
  return (
    <div className="starfield">
      {stars.map((s, i) => (
        <span
          key={i}
          className="starfield__dot"
          style={{
            left: `${s.x}%`,
            top: `${s.y}%`,
            width: `${s.r}px`,
            height: `${s.r}px`,
            animationDuration: `${s.d}s`,
            animationDelay: `${s.t}s`,
            '--o': s.o,
          }}
        />
      ))}
    </div>
  );
}

function TweaksPanel({ tweaks, onChange, kidName, onName, onReset, onClose }) {
  return (
    <div className="tweaks">
      <div className="tweaks__head">
        <div className="tweaks__title">Tweaks</div>
        <button className="iconbtn" onClick={onClose}>
          <svg width="12" height="12" viewBox="0 0 12 12"><path d="M2 2 L10 10 M10 2 L2 10" stroke="currentColor" strokeWidth="1.5"/></svg>
        </button>
      </div>

      <label className="field">
        <span className="field__label mono">OBSERVER NAME</span>
        <input value={kidName} onChange={e => onName(e.target.value)} />
      </label>

      <div className="field">
        <div className="field__label mono">THEME</div>
        <div className="chips">
          {['dark', 'light'].map(t => (
            <button key={t} className="chip" data-active={tweaks.theme === t} onClick={() => onChange('theme', t)}>
              {t === 'dark' ? 'Night sky' : 'Daylight'}
            </button>
          ))}
        </div>
      </div>

      <div className="field">
        <div className="field__label mono">CARD DENSITY</div>
        <div className="chips">
          {['cozy', 'compact'].map(d => (
            <button key={d} className="chip" data-active={tweaks.density === d} onClick={() => onChange('density', d)}>{d}</button>
          ))}
        </div>
      </div>

      <div className="field">
        <div className="field__label mono">MOTION</div>
        <div className="chips">
          {[['full', 'On'], ['reduced', 'Reduced']].map(([k, label]) => (
            <button key={k} className="chip" data-active={(tweaks.motion || 'full') === k} onClick={() => onChange('motion', k)}>{label}</button>
          ))}
        </div>
      </div>

      <button className="btn-ghost" onClick={onReset}>Clear all sightings</button>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
