// fantasy/src/shared.jsx — DataProvider, toasts and helpers for the
// Fantasy Football Control Center. Runs first; publishes window.FantasyShared.
//
// What the provider loads (all under users/{uid}/):
//   fantasy/config, fantasy/playersMeta, derived/fantasySnapshot (the
//   derived product every card reads for projections, legality and health)
//   fantasyLeagues/* with each league's rosters/* and matchups/{currentWeek}
//   fantasyPlayers/{id} — ONLY the ids that appear on a linked league's
//   rosters, fetched in documentId-in chunks. The master is ~3k rows and the
//   dashboard needs ~200 of them; reading the whole thing on every load is
//   the kind of bill that looks fine in September.

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

const DataContext = createContext({});
const ToastContext = createContext(() => {});

function ToastProvider({ children }) {
  const [toast, setToast] = useState(null);
  const show = useCallback((msg, kind = 'success') => {
    setToast({ msg, kind });
    setTimeout(() => setToast(null), 4500);
  }, []);
  return (
    <ToastContext.Provider value={show}>
      {children}
      {toast && <div className={`toast ${toast.kind}`}>{toast.msg}</div>}
    </ToastContext.Provider>
  );
}
const useToast = () => useContext(ToastContext);

const tsToMs = (v) => (v && typeof v.toMillis === 'function') ? v.toMillis() : (typeof v === 'number' ? v : null);

const timeAgo = (v) => {
  const ms = tsToMs(v);
  if (!ms) return 'never';
  const s = Math.max(0, (Date.now() - ms) / 1000);
  if (s < 60) return 'just now';
  if (s < 3600) return `${Math.round(s / 60)}m ago`;
  if (s < 86400) return `${Math.round(s / 3600)}h ago`;
  return `${Math.round(s / 86400)}d ago`;
};

// Firestore's `in` query takes at most 30 ids per call on this SDK.
const ID_CHUNK = 30;

async function loadPlayersByIds(uid, ids) {
  const out = {};
  const unique = [...new Set(ids.filter(Boolean).map(String))];
  const col = db.collection('users').doc(uid).collection('fantasyPlayers');
  for (let i = 0; i < unique.length; i += ID_CHUNK) {
    const chunk = unique.slice(i, i + ID_CHUNK);
    const snap = await col.where(firebase.firestore.FieldPath.documentId(), 'in', chunk).get();
    snap.forEach(d => { out[d.id] = d.data(); });
  }
  return out;
}

function DataProvider({ uid, children }) {
  const [data, setData] = useState({
    config: null, playersMeta: null, snapshot: null, waiverPlan: null, news: [], leagues: [], players: {},
    loading: true, loaded: false, loadError: null,
  });

  const loadAll = useCallback(async () => {
    if (!uid) return;
    const u = db.collection('users').doc(uid);
    setData(d => ({ ...d, loading: true }));
    try {
      const [cfgSnap, metaSnap, snapSnap, planSnap, newsSnap, leagueSnap] = await Promise.all([
        u.collection('fantasy').doc('config').get().catch(() => null),
        u.collection('fantasy').doc('playersMeta').get().catch(() => null),
        u.collection('derived').doc('fantasySnapshot').get().catch(() => null),
        u.collection('derived').doc('fantasyWaiverPlan').get().catch(() => null),
        u.collection('fantasyNews').orderBy('publishedMs', 'desc').limit(25).get().catch(() => null),
        u.collection('fantasyLeagues').get(),
      ]);
      const leagues = await Promise.all(leagueSnap.docs.map(async (doc) => {
        const league = { docId: doc.id, ...doc.data() };
        const [rosterSnap, matchupSnap] = await Promise.all([
          doc.ref.collection('rosters').get().catch(() => null),
          league.currentWeek ? doc.ref.collection('matchups').doc(String(league.currentWeek)).get().catch(() => null) : Promise.resolve(null),
        ]);
        league.rosters = rosterSnap ? rosterSnap.docs.map(d => d.data()) : [];
        league.matchup = matchupSnap && matchupSnap.exists ? matchupSnap.data() : null;
        return league;
      }));
      const ids = leagues.flatMap(l => l.rosters.flatMap(r => [...(r.players || []), ...(r.reserve || []), ...(r.taxi || [])]));
      const players = await loadPlayersByIds(uid, ids);
      setData({
        config: cfgSnap && cfgSnap.exists ? cfgSnap.data() : null,
        playersMeta: metaSnap && metaSnap.exists ? metaSnap.data() : null,
        snapshot: snapSnap && snapSnap.exists ? snapSnap.data() : null,
        waiverPlan: planSnap && planSnap.exists ? planSnap.data() : null,
        news: newsSnap ? newsSnap.docs.map(d => d.data()) : [],
        leagues: leagues.sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''))),
        players,
        loading: false, loaded: true, loadError: null,
      });
    } catch (e) {
      console.error('fantasy load failed', e);
      setData(d => ({ ...d, loading: false, loaded: true, loadError: e.message || String(e) }));
    }
  }, [uid]);

  useEffect(() => { loadAll(); }, [loadAll]);

  const value = useMemo(() => ({ ...data, uid, reload: loadAll }), [data, uid, loadAll]);
  return <DataContext.Provider value={value}>{children}</DataContext.Provider>;
}

function DataGate({ children }) {
  const { loaded, loadError, reload } = useContext(DataContext);
  if (!loaded) return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '60vh', gap: 12 }}>
      <div className="spinner" style={{ width: 28, height: 28 }} />
      <div style={{ fontSize: 12, color: '#64748b' }}>Loading your leagues…</div>
    </div>
  );
  return (
    <>
      {loadError && (
        <div style={{ maxWidth: 1280, margin: '12px auto 0', padding: '0 20px' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 8, padding: '10px 14px', fontSize: 12 }}>
            <span style={{ color: '#ef4444', fontWeight: 600, flexShrink: 0 }}>Data failed to load</span>
            <span style={{ color: '#94a3b8', flex: 1 }}>{loadError}</span>
            <button className="btn-secondary" style={{ fontSize: 11, padding: '4px 10px' }} onClick={reload}>Retry</button>
          </div>
        </div>
      )}
      {children}
    </>
  );
}

// A league's sync state as a chip. Mirrors pipelineHealth's four states in
// spirit: ok / failing / never synced. ("late" arrives with the cron in
// Phase 1, when a cadence exists to be late against.)
function SyncChip({ league }) {
  const h = league.syncHealth || {};
  if (h.state === 'failing') return <span className="tag tag-red" title={h.lastError || ''}>sync failing ×{h.consecutiveFailures || 1}</span>;
  if (h.state === 'ok') return <span className="tag tag-green">synced {timeAgo(h.lastSyncAt)}</span>;
  return <span className="tag tag-grey">never synced</span>;
}

const INJURY_TAG = { Out: 'tag-red', IR: 'tag-red', PUP: 'tag-red', Sus: 'tag-red', Doubtful: 'tag-red', Questionable: 'tag-amber', NA: 'tag-grey' };

// "12.4" / "12.4 (ESPN only)" / "—" — the one renderer for a blended
// projection, so the card and the coach say the same thing (ProjectionBlend.label).
const projLabel = (p) => (p && Number.isFinite(p.projected)) ? ProjectionBlend.label({ points: p.projected, n: (p.sources || []).length, sources: p.sources || [] }) : '—';

const kickoffLabel = (ms) => {
  if (!Number.isFinite(ms)) return '';
  const d = new Date(ms);
  return d.toLocaleString('en-US', { weekday: 'short', hour: 'numeric', minute: '2-digit', timeZone: 'America/New_York' });
};
function InjuryTag({ status }) {
  if (!status) return null;
  return <span className={`tag ${INJURY_TAG[status] || 'tag-grey'}`} style={{ marginLeft: 6 }}>{status}</span>;
}

window.FantasyShared = {
  DataContext, ToastProvider, useToast, DataProvider, DataGate,
  SyncChip, InjuryTag, timeAgo, tsToMs, projLabel, kickoffLabel,
};
