// Shared helpers, store, components used across screens
// Exports to window: store, useStore, fmt, Avatar, DriverPick, TeamPick, Logo, Countdown, etc.

const { useState, useEffect, useMemo, useRef, useCallback } = React;

// ---- Supabase client (null = Demo-Modus ohne Backend) -------------------
const _supabase = (window.SUPABASE_URL && window.SUPABASE_ANON_KEY && window.supabase)
  ? window.supabase.createClient(window.SUPABASE_URL, window.SUPABASE_ANON_KEY)
  : null;
// Expose for other files (screens-1, screens-3)
window._supabase = _supabase;

// ---- Persistent cache (localStorage) ------------------------------------
const LIVE_CACHE_KEY = "f1live_cache_v1";

function _hasStoredSession() {
  try {
    return Object.keys(localStorage).some(k => k.startsWith('sb-') && k.endsWith('-auth-token'));
  } catch { return false; }
}
function _saveCache(state) {
  try {
    localStorage.setItem(LIVE_CACHE_KEY, JSON.stringify({
      user: state.user, currentRaceId: state.currentRaceId,
      seasonPicks: state.seasonPicks, racePicks: state.racePicks,
      results: state.results, raceState: state.raceState, scoring: state.scoring,
      players:  window.SEED.players,
      drivers:  window.SEED.drivers,   // inkl. active-Flag aus API-Sync
      calendar: window.SEED.calendar,  // aktueller Kalender aus API-Sync
      season:   window.SEED.season,
    }));
  } catch {}
}
function _loadCache() {
  try { return JSON.parse(localStorage.getItem(LIVE_CACHE_KEY)); } catch { return null; }
}
function _clearCache() {
  try { localStorage.removeItem(LIVE_CACHE_KEY); } catch {}
}

const defaultState = () => ({
  user:          null,      // { id, name, email, isAdmin }
  view:          _hasStoredSession() ? "loading" : "auth",
  currentRaceId: null,
  seasonPicks:   {},        // { [userId]: { driverChampion, constructor, ... } }
  racePicks:     {},        // { [raceId]: { [userId]: { pole, winner, ... } } }
  results:       {},        // { [raceId]: { pole, winner, sprintWinner, finishers } }
  raceState:     {},        // { [raceId]: 'upcoming' | 'pre-race' | 'finished' }
  scoring:       null,      // null = SEED.scoring Defaults
  authError:     null,      // 'not_registered' | null
});


// ---- Lightweight store ---------------------------------------------------
function _deriveMissingRaceStates(raceState) {
  // Rennstatus aus Datum ableiten / korrigieren:
  // - Vergangene Rennen (> 6h nach Rennstart) → immer 'finished', auch wenn Cache veraltet ist
  // - Zukünftige Rennen ohne Eintrag → aus Datum ableiten
  // - Zukünftige Rennen mit bestehendem Eintrag → unverändert lassen
  const now = Date.now();
  const updated = { ...raceState };
  (window.SEED?.calendar || []).forEach(race => {
    const raceTs = new Date(race.date + 'T15:00:00Z').getTime();
    const preTs  = raceTs - 2 * 24 * 3600 * 1000;
    if (now > raceTs + 6 * 3600 * 1000) {
      updated[race.id] = 'finished'; // Rennen liegt eindeutig in der Vergangenheit
    } else if (!updated[race.id]) {
      updated[race.id] = now >= preTs ? 'pre-race' : 'upcoming';
    }
  });
  return updated;
}

function _initLiveState() {
  if (!_hasStoredSession()) return { ...defaultState(), view: 'auth' };
  const cache = _loadCache();
  if (cache?.user) {
    // Sofortiger Restore aus Cache – Supabase läuft im Hintergrund nach
    if (cache.players)                        window.SEED.players  = cache.players;
    if (cache.drivers)                        window.SEED.drivers  = cache.drivers;
    if (cache.calendar && cache.calendar.length > 0) window.SEED.calendar = cache.calendar;
    if (cache.season)                         window.SEED.season   = cache.season;
    const raceState = _deriveMissingRaceStates(cache.raceState || {});
    const cal = window.SEED?.calendar || [];
    const currentRaceId =
      cal.find(r => raceState[r.id] !== 'finished')?.id ||
      cache.currentRaceId ||
      cal[cal.length - 1]?.id;
    return {
      ...defaultState(),
      user: cache.user, currentRaceId,
      seasonPicks: cache.seasonPicks || {}, racePicks: cache.racePicks || {},
      results: cache.results || {}, raceState,
      scoring: cache.scoring || null,
      view: 'dashboard',
    };
  }
  return { ...defaultState() };
}
let _state = _initLiveState();
const _subs = new Set();

function getState() { return _state; }

function setState(updater) {
  const prev = _state;
  const next = typeof updater === "function" ? updater(prev) : { ...prev, ...updater };
  _state = next;
  _subs.forEach(fn => fn(next));
  if (next.user) {
    _syncToSupabase(prev, next);
    // Cache sofort aktualisieren wenn Picks geändert — sonst gehen sie beim Reload verloren
    if (next.racePicks !== prev.racePicks || next.seasonPicks !== prev.seasonPicks) {
      _saveCache(next);
    }
  }
}

function useStore(selector = s => s) {
  const [, force] = useState(0);
  useEffect(() => {
    const fn = () => force(x => x + 1);
    _subs.add(fn);
    return () => _subs.delete(fn);
  }, []);
  return selector(_state);
}


// ---- Supabase: Daten nach DB schreiben (fire-and-forget) ----------------
function _syncToSupabase(prev, next) {
  if (!next.user) return;
  const userId = next.user.id;

  // Saison-Tipps
  const nextSP = next.seasonPicks?.[userId];
  if (nextSP && nextSP !== prev.seasonPicks?.[userId]) {
    _supabase.from('season_picks').upsert({
      user_id: userId,
      driver_champion:  nextSP.driverChampion  || null,
      constructor:      Object.prototype.hasOwnProperty.call(nextSP, 'constructor') ? (nextSP.constructor || null) : null,
      destructor:       nextSP.destructor       || null,
      driver_kicked:    nextSP.driverKicked     || null,
      team_boss_kicked: nextSP.teamBossKicked   || null,
      surprise:         nextSP.surprise         || null,
      updated_at:       new Date().toISOString(),
    }).then(({ error }) => error && console.error('[sync] season_picks:', error));
  }

  // Renn-Tipps (nur eigene)
  Object.entries(next.racePicks || {}).forEach(([raceId, picks]) => {
    const nextRP = picks?.[userId];
    if (nextRP && nextRP !== prev.racePicks?.[raceId]?.[userId]) {
      _supabase.from('race_picks').upsert({
        user_id:       userId,
        race_id:       raceId,
        pole:          nextRP.pole          || null,
        winner:        nextRP.winner        || null,
        sprint_pole:   nextRP.sprintPole    || null,
        sprint_winner: nextRP.sprintWinner  || null,
        finishers:     nextRP.finishers     ?? null,
        updated_at:    new Date().toISOString(),
      }).then(({ error }) => error && console.error('[sync] race_picks:', error));
    }
  });

  // Admin-only: Ergebnisse, Rennstatus, Scoring
  if (!next.user.isAdmin) return;

  Object.entries(next.results || {}).forEach(([raceId, res]) => {
    if (res && res !== prev.results?.[raceId]) {
      _supabase.from('results').upsert({
        race_id:       raceId,
        pole:          res.pole          || null,
        winner:        res.winner        || null,
        sprint_pole:   res.sprintPole    || null,
        sprint_winner: res.sprintWinner  || null,
        finishers:     res.finishers     ?? null,
        updated_at:    new Date().toISOString(),
      }).then(({ error }) => error && console.error('[sync] results:', error));
    }
  });

  Object.entries(next.raceState || {}).forEach(([raceId, state]) => {
    if (state !== prev.raceState?.[raceId]) {
      _supabase.from('race_states').upsert({
        race_id: raceId, state, updated_at: new Date().toISOString(),
      }).then(({ error }) => error && console.error('[sync] race_states:', error));
    }
  });

  if (next.scoring !== prev.scoring && next.scoring) {
    _supabase.from('scoring_config').upsert({
      id: 1, config: next.scoring, updated_at: new Date().toISOString(),
    }).then(({ error }) => error && console.error('[sync] scoring_config:', error));
  }
}

// ---- Supabase: Alle Daten laden -----------------------------------------
async function _loadAllData(userId, myPlayer) {
  const [
    { data: playersData },
    { data: seasonPicksData },
    { data: racePicksData },
    { data: resultsData },
    { data: raceStatesData },
    { data: scoringRow },
  ] = await Promise.all([
    _supabase.from('players').select('*'),
    _supabase.from('season_picks').select('*'),
    _supabase.from('race_picks').select('*'),
    _supabase.from('results').select('*'),
    _supabase.from('race_states').select('*'),
    _supabase.from('scoring_config').select('config').eq('id', 1).maybeSingle(),
    window.f1Api?.syncCalendar().catch(() => null),
    window.f1Api?.syncDrivers().catch(() => null),
  ]);

  // Spieler-Array aufbauen und SEED.players überschreiben
  const players = (playersData || [])
    .filter(p => p.user_id)   // nur bereits eingeloggte Spieler
    .map(p => ({
      id:      p.user_id,
      name:    p.name,
      avatar:  p.avatar || '🏎',
      email:   p.email,
      isAdmin: p.is_admin,
      you:     p.email === myPlayer.email,
    }));
  window.SEED.players = players;  // bestehenden UI-Code unverändert lassen

  // Saison-Tipps
  const seasonPicks = {};
  (seasonPicksData || []).forEach(sp => {
    seasonPicks[sp.user_id] = {
      driverChampion:  sp.driver_champion,
      constructor:     sp.constructor,
      destructor:      sp.destructor,
      driverKicked:    sp.driver_kicked,
      teamBossKicked:  sp.team_boss_kicked,
      surprise:        sp.surprise,
    };
  });

  // Renn-Tipps
  const racePicks = {};
  (racePicksData || []).forEach(rp => {
    if (!racePicks[rp.race_id]) racePicks[rp.race_id] = {};
    racePicks[rp.race_id][rp.user_id] = {
      pole: rp.pole, winner: rp.winner,
      sprintPole: rp.sprint_pole, sprintWinner: rp.sprint_winner, finishers: rp.finishers,
    };
  });

  // Ergebnisse
  const results = {};
  (resultsData || []).forEach(r => {
    results[r.race_id] = {
      pole: r.pole, winner: r.winner,
      sprintPole: r.sprint_pole, sprintWinner: r.sprint_winner, finishers: r.finishers,
    };
  });

  // Renn-Status aus DB
  const raceState = {};
  (raceStatesData || []).forEach(rs => { raceState[rs.race_id] = rs.state; });

  // Fehlende Rennstatus aus Datum ableiten (DB-Einträge haben Vorrang)
  Object.assign(raceState, _deriveMissingRaceStates(raceState));

  // Aktuelles Rennen = erstes, das nicht "finished" ist
  const cal = window.SEED.calendar;
  const currentRaceId =
    cal.find(r => raceState[r.id] !== 'finished')?.id || cal[cal.length - 1].id;

  _state = {
    ...defaultState(),
    user: {
      id:          userId,
      name:        myPlayer.name,
      email:       myPlayer.email,
      avatar:      myPlayer.avatar || '🏎',
      isAdmin:     myPlayer.is_admin,
      notifyEmail: myPlayer.notify_email || false,
    },
    seasonPicks, racePicks, results, raceState,
    scoring:       scoringRow?.config || null,
    view:          'dashboard',
    currentRaceId,
    authError:     null,
  };
  _saveCache(_state);
  _subs.forEach(fn => fn(_state));

  // Auto-API-Sync: Saison-Ergebnisse nur für Admins (Kalender+Fahrer bereits oben geladen)
  if (window.f1Api && myPlayer.is_admin) {
    window.f1Api.syncSeason()
      .then(apiData => {
        if (!apiData) return;
        setState(s => {
          const raceState = _deriveMissingRaceStates({
            ...s.raceState,
            ...(apiData.raceState || {}),
          });
          const cal = window.SEED.calendar;
          const currentRaceId =
            cal.find(r => raceState[r.id] !== 'finished')?.id ||
            cal[cal.length - 1]?.id;
          return { ...s, raceState, currentRaceId, results: { ...s.results, ...apiData.results } };
        });
        _saveCache(getState());
        console.log('[f1] Admin-Sync abgeschlossen');
      })
      .catch(err => console.warn('[f1] Admin-Sync fehlgeschlagen:', err));
  }
}

// ---- Supabase: Login verarbeiten ----------------------------------------
async function _handleSignIn(session) {
  // Prüfen ob E-Mail in der players-Tabelle steht
  const { data: player, error: playerError } = await _supabase
    .from('players')
    .select('*')
    .eq('email', session.user.email)
    .maybeSingle();

  if (playerError) throw new Error('players query: ' + playerError.message);

  if (!player) {
    // Nicht registriert → sofort ausloggen
    _clearCache();
    await _supabase.auth.signOut();
    _state = { ...defaultState(), view: 'auth', authError: 'not_registered' };
    _subs.forEach(fn => fn(_state));
    return;
  }

  // Beim ersten Login: user_id in players-Tabelle eintragen
  if (!player.user_id) {
    await _supabase.from('players')
      .update({ user_id: session.user.id })
      .eq('email', session.user.email);
  }

  await _loadAllData(session.user.id, { ...player, user_id: session.user.id });
}

// ---- Supabase: Session verarbeiten (öffentlich, für direkten Aufruf) ----
async function processSignIn(session) {
  try {
    await _handleSignIn(session);
  } catch (err) {
    console.error('[f1] processSignIn failed:', err);
    _state = { ...defaultState(), view: 'auth', authError: 'sign_in_failed' };
    _subs.forEach(fn => fn(_state));
    throw err;
  }
}

// ---- Supabase: Stille Daten-Aktualisierung (nach Cache-Restore) ---------
// Lädt Picks, Ergebnisse und Rennstatus frisch aus der DB, ohne View zu ändern.
async function _silentRefresh() {
  try {
    const [
      { data: racePicksData },
      { data: seasonPicksData },
      { data: resultsData },
      { data: raceStatesData },
      { data: playersData },
      { data: scoringRow },
    ] = await Promise.all([
      _supabase.from('race_picks').select('*'),
      _supabase.from('season_picks').select('*'),
      _supabase.from('results').select('*'),
      _supabase.from('race_states').select('*'),
      _supabase.from('players').select('*'),
      _supabase.from('scoring_config').select('config').eq('id', 1).maybeSingle(),
      window.f1Api?.syncCalendar().catch(() => null),
    ]);

    const racePicks = {};
    (racePicksData || []).forEach(rp => {
      if (!racePicks[rp.race_id]) racePicks[rp.race_id] = {};
      racePicks[rp.race_id][rp.user_id] = {
        pole: rp.pole, winner: rp.winner,
        sprintPole: rp.sprint_pole, sprintWinner: rp.sprint_winner, finishers: rp.finishers,
      };
    });

    const seasonPicks = {};
    (seasonPicksData || []).forEach(sp => {
      seasonPicks[sp.user_id] = {
        driverChampion:  sp.driver_champion,
        constructor:     Object.prototype.hasOwnProperty.call(sp, 'constructor') ? sp.constructor : null,
        destructor:      sp.destructor,
        driverKicked:    sp.driver_kicked,
        teamBossKicked:  sp.team_boss_kicked,
        surprise:        sp.surprise,
      };
    });

    const results = {};
    (resultsData || []).forEach(r => {
      results[r.race_id] = {
        pole: r.pole, winner: r.winner,
        sprintPole: r.sprint_pole, sprintWinner: r.sprint_winner, finishers: r.finishers,
      };
    });

    const raceStateFromDb = {};
    (raceStatesData || []).forEach(rs => { raceStateFromDb[rs.race_id] = rs.state; });
    const raceState = _deriveMissingRaceStates(raceStateFromDb);

    if (playersData) {
      const myEmail = getState().user?.email;
      window.SEED.players = playersData
        .filter(p => p.user_id)
        .map(p => ({
          id: p.user_id, name: p.name, avatar: p.avatar || '🏎',
          email: p.email, isAdmin: p.is_admin, you: p.email === myEmail,
        }));
    }

    const cal = window.SEED?.calendar || [];
    const currentRaceId =
      cal.find(r => raceState[r.id] !== 'finished')?.id ||
      cal[cal.length - 1]?.id;

    const scoring = scoringRow?.config !== undefined ? scoringRow.config : getState().scoring;
    setState(s => ({ ...s, racePicks, seasonPicks, results, raceState, currentRaceId, scoring }));
    _saveCache(getState());
    console.log('[f1] Silent refresh abgeschlossen');
  } catch (err) {
    console.warn('[f1] Silent refresh fehlgeschlagen:', err);
  }
}

// ---- Supabase: Session beim Start laden + Auth-Events -------------------
// Primär: getSession() direkt – zuverlässiger als onAuthStateChange INITIAL_SESSION
_supabase.auth.getSession().then(async ({ data: { session } }) => {
  if (getState().view !== 'loading') {
    // Cache hat bereits geladen — Daten still aus DB aktualisieren (Picks, Ergebnisse)
    if (session) _silentRefresh();
    return;
  }
  if (session) {
    try { await _handleSignIn(session); }
    catch (err) {
      console.error('[f1] getSession handleSignIn failed:', err);
      _state = { ...defaultState(), view: 'auth', authError: 'sign_in_failed' };
      _subs.forEach(fn => fn(_state));
    }
  } else {
    _state = { ...defaultState(), view: 'auth' };
    _subs.forEach(fn => fn(_state));
  }
}).catch(err => {
  console.error('[f1] getSession failed:', err);
  if (getState().view === 'loading') {
    _state = { ...defaultState(), view: 'auth' };
    _subs.forEach(fn => fn(_state));
  }
});

// Sicherheits-Timeout: falls nach 10s noch im loading-State, auf auth fallen
setTimeout(() => {
  if (getState().view === 'loading') {
    _state = { ...defaultState(), view: 'auth' };
    _subs.forEach(fn => fn(_state));
  }
}, 10000);

// Periodische Rennstatus-Korrektur: verhindert veraltete Zustände bei langem Tab-Betrieb.
// Läuft alle 15 Minuten und korrigiert raceState + currentRaceId auf Basis der aktuellen Zeit.
// (Beispiel: Monaco wurde "pre-race" gespeichert, Tab blieb offen → nächster Tick → 'finished')
setInterval(() => {
  const s = getState();
  if (!s.user) return;
  const raceState = _deriveMissingRaceStates(s.raceState);
  const cal = window.SEED?.calendar || [];
  const currentRaceId =
    cal.find(r => raceState[r.id] !== 'finished')?.id ||
    cal[cal.length - 1]?.id;
  setState(prev => ({ ...prev, raceState, currentRaceId }));
}, 15 * 60 * 1000);

// Backup: Magic-Link-Redirect feuert SIGNED_IN nach URL-Hash-Verarbeitung
_supabase.auth.onAuthStateChange(async (event, session) => {
  if (event === 'SIGNED_IN' && session && !getState().user) {
    try { await _handleSignIn(session); }
    catch (err) {
      console.error('[f1] onAuthStateChange handleSignIn failed:', err);
      _state = { ...defaultState(), view: 'auth', authError: 'sign_in_failed' };
      _subs.forEach(fn => fn(_state));
    }
  }
});

// ---- Öffentliche Auth-Funktion ------------------------------------------
async function signOut() {
  _clearCache();
  await _supabase.auth.signOut();
  _state = { ...defaultState(), view: 'auth' };
  _subs.forEach(fn => fn(_state));
}

// ---- Util ---------------------------------------------------------------
// Gibt immer ein Objekt zurück (Fallback für gekickte/unbekannte Fahrer)
function getDriver(id) {
  return window.SEED.drivers.find(d => d.id === id)
    || (id ? { id, num: '?', first: '', last: id, team: null, code: id.slice(0,3).toUpperCase(), active: false, _unknown: true } : null);
}
function getTeam(id) {
  return window.SEED.teams.find(t => t.id === id)
    || { id: id || '?', name: id || '?', short: '?', color: 'var(--bg-3)', accent: 'var(--ink-2)' };
}
function getRace(id)   { return window.SEED.calendar.find(r => r.id === id); }
function driverName(id, short) {
  const d = getDriver(id); if (!d) return "—";
  return short ? `${d.last}` : `${d.first} ${d.last}`;
}
function teamName(id) { const t = getTeam(id); return t ? t.name : "—"; }

function formatDate(iso) {
  const d = new Date(iso);
  const months = ["JAN","FEB","MAR","APR","MAI","JUN","JUL","AUG","SEP","OKT","NOV","DEZ"];
  const day = String(d.getDate()).padStart(2, "0");
  return `${day} ${months[d.getMonth()]}`;
}

function getDeadlines(race) {
  const raceTs = new Date(race.date + "T15:00:00Z").getTime();
  // FP1 aus API wenn vorhanden, Fallback: Freitag 11:30 UTC (≈ 2 Tage vor Rennen)
  const fp1Ts = race.fp1
    ? new Date(race.fp1).getTime()
    : raceTs - 2 * 24 * 3600 * 1000 - 3.5 * 3600 * 1000;
  return { fp1: fp1Ts };
}

// ---- useNow: gibt aktuelle Zeit zurück, re-rendert alle N ms -------------
function useNow(ms) {
  const [now, setNow] = React.useState(Date.now);
  React.useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), ms || 30000);
    return () => clearInterval(id);
  }, [ms]);
  return now;
}

// ---- Toast: globale Benachrichtigung -------------------------------------
let _toastFn = null;
function showToast(msg, type) {
  if (_toastFn) _toastFn(msg, type || 'ok');
}
function Toast() {
  const [item, setItem] = React.useState(null);
  const timerRef = React.useRef(null);
  React.useEffect(() => {
    _toastFn = (msg, type) => {
      setItem({ msg, type });
      clearTimeout(timerRef.current);
      timerRef.current = setTimeout(() => setItem(null), 2500);
    };
    return () => { _toastFn = null; };
  }, []);
  if (!item) return null;
  const color = item.type === 'err' ? 'var(--warn)' : 'var(--good)';
  return (
    <div style={{
      position: 'fixed', bottom: 28, left: '50%', transform: 'translateX(-50%)',
      background: 'var(--bg-2)', border: `1px solid ${color}`, color,
      padding: '9px 20px', borderRadius: 6, fontSize: 12, fontWeight: 700,
      letterSpacing: '0.1em', zIndex: 9999, pointerEvents: 'none',
      boxShadow: '0 4px 16px rgba(0,0,0,0.5)',
    }}>
      {item.msg}
    </div>
  );
}

// ---- Visual atoms -------------------------------------------------------
function Logo({ size = 140, styleOverride = {}, className = "" }) {
  if (window.F1_LOGO_SRC) {
    const style = className
      ? { display: "block", ...styleOverride }
      : { width: size, height: "auto", display: "block", ...styleOverride };
    return <img src={window.F1_LOGO_SRC} alt="F1 Tippspiel" className={className} style={style} />;
  }
  // Fallback — Bebas Neue hat kein Italic/Bold, daher ohne fontStyle/fontWeight
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{
        background: "var(--accent)", padding: "4px 10px",
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        <span style={{
          fontFamily: "var(--font-display)", fontSize: 22,
          color: "#fff", letterSpacing: "0.05em", lineHeight: 1,
        }}>F1</span>
      </div>
      <div style={{ display: "flex", flexDirection: "column", lineHeight: 1.1 }}>
        <span style={{
          fontFamily: "var(--font-display)", fontSize: 20,
          letterSpacing: "0.06em", textTransform: "uppercase",
        }}>TIPPSPIEL</span>
        <span className="h-mono" style={{ fontSize: 9, color: "var(--ink-2)", letterSpacing: "0.18em" }}>
          BOX/26 · PRIVATE GROUP
        </span>
      </div>
    </div>
  );
}

function TeamLogo({ teamId, size = 24 }) {
  const t = getTeam(teamId); if (!t) return null;
  return (
    <div style={{
      width: size, height: size, background: t.color, border: `1px solid ${t.accent}`,
      display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
      clipPath: "polygon(0 0, 100% 0, 85% 100%, 0 100%)",
    }}>
      <span style={{ fontFamily: "var(--font-display)", fontSize: size * 0.45, color: t.accent, letterSpacing: "0.04em" }}>
        {t.short}
      </span>
    </div>
  );
}

function DriverNum({ num, teamId, size = 36 }) {
  const t = getTeam(teamId);
  return (
    <div style={{
      width: size, height: size,
      background: t ? t.color : "var(--bg-3)",
      borderLeft: t ? `3px solid ${t.accent}` : "3px solid var(--accent)",
      display: "flex", alignItems: "center", justifyContent: "center",
      fontFamily: "var(--font-display)", fontSize: size * 0.46,
      color: t ? t.accent : "var(--ink-0)", flexShrink: 0, overflow: "hidden",
    }}>{num}</div>
  );
}

function Avatar({ player, size = 32 }) {
  return (
    <div className="avatar" style={{ width: size, height: size, fontSize: size * 0.5 }}>
      {player.avatar}
    </div>
  );
}

function FlagDot({ country }) {
  return (
    <span className="h-mono" style={{
      fontSize: 10, padding: "2px 6px", border: "1px solid var(--line-2)",
      color: "var(--ink-1)", letterSpacing: "0.1em",
    }}>{country}</span>
  );
}

function Countdown({ ts, compact }) {
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);
  const diff = Math.max(0, ts - now);
  const d = Math.floor(diff / 86400000);
  const h = Math.floor((diff % 86400000) / 3600000);
  const m = Math.floor((diff % 3600000) / 60000);
  const s = Math.floor((diff % 60000) / 1000);
  const expired = diff <= 0;
  if (compact) {
    return (
      <span className="tnum" style={{ color: expired ? "var(--bad)" : "var(--ink-0)" }}>
        {expired ? "GESCHLOSSEN" : `${d}T ${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}:${String(s).padStart(2,"0")}`}
      </span>
    );
  }
  return (
    <div className="countdown">
      <div className="box"><div className="num tnum">{String(d).padStart(2,"0")}</div><div className="lbl">Tage</div></div>
      <div className="box"><div className="num tnum">{String(h).padStart(2,"0")}</div><div className="lbl">Std</div></div>
      <div className="box"><div className="num tnum">{String(m).padStart(2,"0")}</div><div className="lbl">Min</div></div>
      <div className="box"><div className="num tnum">{String(s).padStart(2,"0")}</div><div className="lbl">Sek</div></div>
    </div>
  );
}

function DriverPick({ driver, selected, onClick, disabled, showTeam = true }) {
  const t = getTeam(driver.team);
  return (
    <button className={`pick ${selected ? "selected" : ""}`} onClick={onClick} disabled={disabled}
            style={{ paddingLeft: 8 }}>
      <DriverNum num={driver.num} teamId={driver.team} size={40} />
      <div style={{ flex: 1, minWidth: 0, paddingLeft: 4 }}>
        <div style={{ fontWeight: 600, fontSize: 14 }}>{driver.first} {driver.last}</div>
        {showTeam && <div className="h-mono" style={{ fontSize: 10, color: "var(--ink-2)", letterSpacing: "0.12em" }}>{t.name.toUpperCase()}</div>}
      </div>
      {selected && <span className="h-mono" style={{ fontSize: 10, color: "var(--accent)", letterSpacing: "0.15em", paddingRight: 4 }}>✓ TIPP</span>}
    </button>
  );
}

function TeamPick({ team, selected, onClick, disabled }) {
  return (
    <button className={`pick ${selected ? "selected" : ""}`} onClick={onClick} disabled={disabled} style={{ paddingLeft: 0 }}>
      <TeamLogo teamId={team.id} size={36} />
      <div style={{ flex: 1, minWidth: 0, paddingLeft: 4 }}>
        <div style={{ fontWeight: 600, fontSize: 14 }}>{team.name}</div>
        <div className="h-mono" style={{ fontSize: 10, color: "var(--ink-2)", letterSpacing: "0.12em" }}>CONSTRUCTOR · {team.short}</div>
      </div>
      {selected && <span className="h-mono" style={{ fontSize: 10, color: "var(--accent)", letterSpacing: "0.15em", marginRight: 12 }}>✓ TIPP</span>}
    </button>
  );
}

function SectionH({ title, sub, right }) {
  return (
    <div className="section-h">
      <div style={{ flex: 1 }}>
        <h2 className="h-display">{title}</h2>
        {sub && <div className="h-tag" style={{ marginTop: 3 }}>{sub}</div>}
      </div>
      {right}
    </div>
  );
}

// ---- Score-Berechnung ---------------------------------------------------
function computeScores() {
  const s = getState();
  const SEED = window.SEED;
  const scoring = s.scoring || SEED.scoring;
  const totals = {};
  SEED.players.forEach(p => totals[p.id] = { total: 0, byRace: {}, byRaceBreakdown: {}, season: 0, seasonBreakdown: {} });

  // Renn-Punkte
  Object.entries(s.results).forEach(([raceId, res]) => {
    const picks = s.racePicks[raceId] || {};
    SEED.players.forEach(p => {
      const pk = picks[p.id]; if (!pk) return;
      const bk = {};
      if (res.pole          && pk.pole          === res.pole)          bk.pole         = scoring.pole;
      if (res.winner        && pk.winner        === res.winner)        bk.raceWinner   = scoring.raceWinner;
      if (res.sprintWinner  && pk.sprintWinner  === res.sprintWinner)  bk.sprintWinner = scoring.sprintWinner;
      if (res.sprintPole    && pk.sprintPole    === res.sprintPole)    bk.sprintPole   = scoring.sprintPole || 0;
      if (res.finishers != null && pk.finishers != null) {
        if (pk.finishers === res.finishers)                            bk.finishers      = scoring.finishers;
        else if (Math.abs(pk.finishers - res.finishers) === 1)        bk.finishersClose = scoring.finishersClose;
      }
      const pts = Object.values(bk).reduce((sum, v) => sum + v, 0);
      totals[p.id].byRace[raceId] = pts;
      totals[p.id].byRaceBreakdown[raceId] = bk;
      totals[p.id].total += pts;
    });
  });

  // Saison-Punkte (einmalig, sobald Admin Saison-Ergebnisse einträgt)
  // Object.create(null) statt {} verhindert, dass prototype-Properties wie .constructor fälschlicherweise matchen
  const sr = scoring.seasonResults || Object.create(null);
  SEED.players.forEach(p => {
    const sp = s.seasonPicks[p.id] || Object.create(null);
    const sbk = {};
    if (sr.driverChampion && sp.driverChampion === sr.driverChampion) sbk.driverChampion = scoring.seasonChampion;
    if (sr.constructor    && sp.constructor    === sr.constructor)    sbk.constructor    = scoring.seasonConstructor;
    if (sr.destructor     && sp.destructor     === sr.destructor)     sbk.destructor     = scoring.seasonDestructor;
    if (sr.driverKicked   && sp.driverKicked   === sr.driverKicked)   sbk.driverKicked   = scoring.seasonDriverKicked;
    if (sr.teamBossKicked && sp.teamBossKicked === sr.teamBossKicked) sbk.teamBossKicked = scoring.seasonTeamBossKicked;
    if (sr.surprise       && sp.surprise       === sr.surprise)       sbk.surprise       = scoring.seasonSurprise;
    const pts = Object.values(sbk).reduce((sum, v) => sum + v, 0);
    totals[p.id].season = pts;
    totals[p.id].seasonBreakdown = sbk;
    totals[p.id].total += pts;
  });

  return { scoring, totals };
}

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  static getDerivedStateFromError(error) {
    return { error };
  }
  componentDidCatch(error, info) {
    console.error('[ErrorBoundary]', error, info?.componentStack);
  }
  render() {
    if (this.state.error) {
      return (
        <div className="container col" style={{ gap: 16, paddingBottom: 40, paddingTop: 24 }}>
          <div className="card" style={{ padding: 24, textAlign: "center" }}>
            <div className="h-display" style={{ fontSize: 48, color: "var(--bad)", marginBottom: 8 }}>!</div>
            <div className="h-tag" style={{ color: "var(--bad)", marginBottom: 8 }}>FEHLER IN DIESER ANSICHT</div>
            <div className="muted" style={{ fontSize: 13, marginBottom: 20 }}>
              Etwas ist schiefgelaufen. Du kannst es erneut versuchen oder zu einer anderen Seite wechseln.
            </div>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-3)", marginBottom: 20,
                          background: "var(--bg-2)", padding: "8px 12px", textAlign: "left", wordBreak: "break-all" }}>
              {this.state.error?.message || "Unbekannter Fehler"}
            </div>
            <button className="btn btn-primary" style={{ justifyContent: "center" }}
                    onClick={() => this.setState({ error: null })}>
              NOCHMAL VERSUCHEN
            </button>
          </div>
        </div>
      );
    }
    return this.props.children;
  }
}

Object.assign(window, {
  // store
  getState, setState, useStore, signOut, processSignIn,
  // util
  getDriver, getTeam, getRace, driverName, teamName, formatDate, getDeadlines,
  // hooks + toast
  useNow, showToast, Toast,
  // atoms
  Logo, TeamLogo, DriverNum, Avatar, FlagDot, Countdown, DriverPick, TeamPick, SectionH,
  // scoring
  computeScores,
  // error handling
  ErrorBoundary,
});
