// Shared components: Mascot, Confetti background, Logo, Card, Button, theme tokens
const { useState, useEffect, useRef, useMemo } = React;

// ---- Theme variants ----
const THEMES = {
  arcade: {
    name: "Arcade Pop",
    bg: "#1a1340",
    bgDeep: "#120a30",
    ink: "#fef6e4",
    inkDim: "#b8a9d9",
    pink: "#ff6b9d",
    yellow: "#ffd23f",
    teal: "#3ddbd9",
    lime: "#a8e635",
    coral: "#ff8a5c",
    border: "#fef6e4",
    cardShadow: "8px 8px 0 #000",
    radius: "28px",
  },
  neobrutal: {
    name: "Neo Brutal",
    bg: "#fef6e4",
    bgDeep: "#f0e6c8",
    ink: "#0a0a0a",
    inkDim: "#5a5a5a",
    pink: "#ff4d8d",
    yellow: "#ffd23f",
    teal: "#2dd4bf",
    lime: "#a3e635",
    coral: "#ff7a3a",
    border: "#0a0a0a",
    cardShadow: "10px 10px 0 #0a0a0a",
    radius: "20px",
  },
  candy: {
    name: "Soft Candy",
    bg: "#2d1b4e",
    bgDeep: "#1f1238",
    ink: "#fef6e4",
    inkDim: "#c4b5e0",
    pink: "#ffb3c8",
    yellow: "#ffe599",
    teal: "#9fe5d9",
    lime: "#c8e6a0",
    coral: "#ffc4a3",
    border: "rgba(255,246,228,0.2)",
    cardShadow: "0 12px 40px rgba(0,0,0,0.4)",
    radius: "32px",
  },
};

function applyTheme(themeKey) {
  const t = THEMES[themeKey] || THEMES.arcade;
  const r = document.documentElement.style;
  r.setProperty("--bg", t.bg);
  r.setProperty("--bg-deep", t.bgDeep);
  r.setProperty("--ink", t.ink);
  r.setProperty("--ink-dim", t.inkDim);
  r.setProperty("--pink", t.pink);
  r.setProperty("--yellow", t.yellow);
  r.setProperty("--teal", t.teal);
  r.setProperty("--lime", t.lime);
  r.setProperty("--coral", t.coral);
  r.setProperty("--border", t.border);
  r.setProperty("--card-shadow", t.cardShadow);
  r.setProperty("--radius", t.radius);
}

// ---- Original mascot: "Svende" the banana adventurer ----
// Built only from circles/ellipses/rects/paths-of-arcs as allowed
function Mascot({ size = 80, mood = "happy" }) {
  return (
    <svg width={size} height={size} viewBox="0 0 100 100" style={{ display: "block" }}>
      {/* shadow */}
      <ellipse cx="50" cy="92" rx="24" ry="3" fill="rgba(0,0,0,0.25)" />
      {/* banana body — curved using ellipse rotation */}
      <g transform="translate(50 52) rotate(-18)">
        <ellipse cx="0" cy="0" rx="22" ry="32" fill="#ffd23f" stroke="#0a0a0a" strokeWidth="3" />
        <ellipse cx="-6" cy="-4" rx="12" ry="24" fill="#fff09a" opacity="0.6" />
        {/* stem */}
        <rect x="-3" y="-34" width="6" height="8" rx="2" fill="#6b4a2b" stroke="#0a0a0a" strokeWidth="2" />
        {/* tip */}
        <ellipse cx="0" cy="32" rx="4" ry="3" fill="#6b4a2b" stroke="#0a0a0a" strokeWidth="2" />
      </g>
      {/* face */}
      <g>
        {/* eye whites */}
        <circle cx="42" cy="48" r="6" fill="#fef6e4" stroke="#0a0a0a" strokeWidth="2" />
        <circle cx="58" cy="46" r="6" fill="#fef6e4" stroke="#0a0a0a" strokeWidth="2" />
        {/* pupils */}
        <circle cx="43" cy="49" r="2.5" fill="#0a0a0a" />
        <circle cx="59" cy="47" r="2.5" fill="#0a0a0a" />
        {/* sparkle */}
        <circle cx="44" cy="48" r="0.8" fill="#fef6e4" />
        <circle cx="60" cy="46" r="0.8" fill="#fef6e4" />
        {/* cheeks */}
        <circle cx="36" cy="56" r="3" fill="#ff6b9d" opacity="0.7" />
        <circle cx="64" cy="54" r="3" fill="#ff6b9d" opacity="0.7" />
        {/* smile - simple arc */}
        {mood === "happy" ? (
          <path d="M 44 58 Q 50 64 56 56" fill="none" stroke="#0a0a0a" strokeWidth="2.5" strokeLinecap="round" />
        ) : (
          <circle cx="50" cy="60" r="3" fill="#0a0a0a" />
        )}
      </g>
      {/* crown / sparkle accent */}
      <g transform="translate(28 22)">
        <polygon points="0,8 4,0 8,8" fill="#ff6b9d" stroke="#0a0a0a" strokeWidth="2" />
      </g>
    </svg>
  );
}

// ---- Confetti background — random dots ----
function ConfettiBg({ count = 40, seed = 1 }) {
  const dots = useMemo(() => {
    const colors = ["var(--pink)", "var(--yellow)", "var(--teal)", "var(--lime)", "var(--coral)"];
    const shapes = ["circle", "square", "diamond", "triangle"];
    const arr = [];
    let s = seed;
    const rand = () => {
      s = (s * 9301 + 49297) % 233280;
      return s / 233280;
    };
    for (let i = 0; i < count; i++) {
      arr.push({
        x: rand() * 100,
        y: rand() * 100,
        size: 6 + rand() * 14,
        color: colors[Math.floor(rand() * colors.length)],
        shape: shapes[Math.floor(rand() * shapes.length)],
        rot: rand() * 360,
        opacity: 0.15 + rand() * 0.35,
      });
    }
    return arr;
  }, [count, seed]);

  return (
    <div
      aria-hidden="true"
      style={{
        position: "fixed",
        inset: 0,
        pointerEvents: "none",
        zIndex: 0,
        overflow: "hidden",
      }}
    >
      {dots.map((d, i) => (
        <div
          key={i}
          style={{
            position: "absolute",
            left: `${d.x}%`,
            top: `${d.y}%`,
            width: d.size,
            height: d.size,
            background: d.shape === "triangle" ? "transparent" : d.color,
            borderRadius: d.shape === "circle" ? "50%" : d.shape === "square" ? "3px" : 0,
            transform: `rotate(${d.rot}deg) ${d.shape === "diamond" ? "rotate(45deg)" : ""}`,
            opacity: d.opacity,
            ...(d.shape === "triangle" && {
              width: 0,
              height: 0,
              background: "transparent",
              borderLeft: `${d.size / 2}px solid transparent`,
              borderRight: `${d.size / 2}px solid transparent`,
              borderBottom: `${d.size}px solid ${d.color}`,
            }),
          }}
        />
      ))}
    </div>
  );
}

// ---- Logo ----
function Logo({ small = false, familyName = null }) {
  return (
    <a
      href="index.html"
      style={{
        display: "flex",
        alignItems: "center",
        gap: 10,
        textDecoration: "none",
        color: "var(--ink)",
      }}
    >
      <Mascot size={small ? 36 : 44} />
      <div style={{ display: "flex", flexDirection: "column", lineHeight: 1 }}>
        <span
          style={{
            fontFamily: "'Space Grotesk', sans-serif",
            fontWeight: 700,
            fontSize: small ? 18 : 22,
            letterSpacing: "-0.02em",
          }}
        >
          Svende<span style={{ color: "var(--yellow)" }}>Banan</span>
        </span>
        <span
          style={{
            fontFamily: "'DM Sans', sans-serif",
            fontSize: 10,
            color: "var(--ink-dim)",
            letterSpacing: "0.15em",
            textTransform: "uppercase",
            marginTop: 2,
          }}
        >
          {familyName || "Family Game Hub"}
        </span>
      </div>
    </a>
  );
}

// ---- Header ----
function Header({ active = "home", family = null }) {
  const linkStyle = (key) => ({
    fontFamily: "'DM Sans', sans-serif",
    fontWeight: 500,
    fontSize: 15,
    color: active === key ? "var(--ink)" : "var(--ink-dim)",
    textDecoration: "none",
    padding: "8px 14px",
    borderRadius: 999,
    background: active === key ? "rgba(255,255,255,0.08)" : "transparent",
    transition: "all 0.15s",
  });
  const [inviteOpen, setInviteOpen] = useState(false);
  return (
    <header
      style={{
        position: "relative",
        zIndex: 10,
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        padding: "20px 32px",
        maxWidth: 1280,
        margin: "0 auto",
        flexWrap: "wrap",
        gap: 16,
      }}
    >
      <Logo familyName={family?.name} />
      <nav style={{ display: "flex", gap: 4, alignItems: "center", flexWrap: "wrap" }}>
        <a href="index.html" style={linkStyle("home")}>Home</a>
        <a href="index.html#games" style={linkStyle("games")}>Games</a>
        <a href="index.html#leaderboard" style={linkStyle("leaderboard")}>Leaderboard</a>
        <a href="index.html#players" style={linkStyle("players")}>Players</a>
        {family && (
          <button
            onClick={() => setInviteOpen(true)}
            style={{
              marginLeft: 4,
              background: "transparent",
              color: "var(--ink)",
              border: "2px solid var(--border)",
              borderRadius: 999,
              padding: "8px 14px",
              fontFamily: "'DM Sans', sans-serif",
              fontWeight: 600,
              fontSize: 13,
              cursor: "pointer",
            }}
          >🎟️ Invite</button>
        )}
        <a
          href="index.html#games"
          style={{
            marginLeft: 8,
            background: "var(--yellow)",
            color: "#0a0a0a",
            border: "2px solid var(--border)",
            borderRadius: 999,
            padding: "10px 20px",
            fontFamily: "'Space Grotesk', sans-serif",
            fontWeight: 700,
            fontSize: 14,
            cursor: "pointer",
            boxShadow: "4px 4px 0 var(--border)",
            textDecoration: "none",
            display: "inline-block",
          }}
        >
          + New Game
        </a>
        <AuthChip />
      </nav>
      {inviteOpen && family && <InviteModal family={family} onClose={() => setInviteOpen(false)} />}
    </header>
  );
}

// Small chip in the header showing sign-in status. Tied to firebase-auth.js
// (which exposes window.svendeAuth). Optional — if Firebase isn't loaded yet
// or the user isn't signed in, shows a "Sign in" button.
function AuthChip() {
  const [user, setUser] = useState(() => window.svendeAuth?.getUser?.() ?? null);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    if (!window.svendeAuth) return;
    return window.svendeAuth.onChange(setUser);
  }, []);

  const signIn = async () => {
    if (!window.svendeAuth) return;
    setBusy(true);
    try { await window.svendeAuth.signIn(); }
    catch (e) { console.warn("sign-in failed:", e.message); }
    finally { setBusy(false); }
  };
  const signOut = async () => {
    if (!window.svendeAuth) return;
    if (!confirm("Sign out? Your in-progress games are kept on this device.")) return;
    await window.svendeAuth.signOut();
  };

  if (!user) {
    return (
      <button onClick={signIn} disabled={busy} style={{
        marginLeft: 8,
        background: "transparent", color: "var(--ink)",
        border: "2px solid var(--border)", borderRadius: 999,
        padding: "8px 16px",
        fontFamily: "'DM Sans', sans-serif",
        fontWeight: 600, fontSize: 13,
        cursor: busy ? "wait" : "pointer", opacity: busy ? 0.6 : 1,
      }}>{busy ? "Signing in…" : "Sign in"}</button>
    );
  }

  const initial = (user.displayName || user.email || "?")[0].toUpperCase();
  return (
    <button onClick={signOut} title={user.email || ""} style={{
      marginLeft: 8,
      display: "flex", alignItems: "center", gap: 8,
      background: "rgba(255,255,255,0.06)", color: "var(--ink)",
      border: "2px solid rgba(255,255,255,0.15)", borderRadius: 999,
      padding: "4px 14px 4px 4px",
      fontFamily: "'DM Sans', sans-serif",
      fontWeight: 600, fontSize: 13, cursor: "pointer",
    }}>
      {user.photoURL ? (
        <img src={user.photoURL} alt="" referrerPolicy="no-referrer" style={{
          width: 26, height: 26, borderRadius: "50%",
          border: "1.5px solid var(--border)",
        }} />
      ) : (
        <span style={{
          width: 26, height: 26, borderRadius: "50%",
          background: "var(--teal)", color: "#0a0a0a",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "'Space Grotesk', sans-serif", fontWeight: 800, fontSize: 12,
          border: "1.5px solid var(--border)",
        }}>{initial}</span>
      )}
      <span style={{ maxWidth: 100, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
        {user.displayName?.split(" ")[0] || user.email}
      </span>
    </button>
  );
}

// ---- Pill / Badge ----
function Pill({ color = "var(--pink)", children }) {
  return (
    <span
      style={{
        display: "inline-block",
        background: color,
        color: "#0a0a0a",
        fontFamily: "'Space Grotesk', sans-serif",
        fontWeight: 700,
        fontSize: 11,
        textTransform: "uppercase",
        letterSpacing: "0.1em",
        padding: "4px 10px",
        borderRadius: 999,
        border: "2px solid var(--border)",
      }}
    >
      {children}
    </span>
  );
}

// ---- Player palette ----
// Roster is per-family (loaded from /api/players). PLAYER_COLORS provides
// the random-pick palette when adding a brand-new player slot.
const PLAYER_COLORS = [
  "#ff6b9d", "#ffd23f", "#3ddbd9", "#a8e635", "#ff8a5c", "#c084fc",
  "#f472b6", "#60a5fa", "#fbbf24", "#34d399", "#fb7185", "#a78bfa",
];

function pickRandomPlayerColor() {
  return PLAYER_COLORS[Math.floor(Math.random() * PLAYER_COLORS.length)];
}

// Cached roster for first paint and offline-resilience.
const ROSTER_CACHE_KEY = "svende-roster-cache";
function getRosterCache() {
  try {
    const raw = localStorage.getItem(ROSTER_CACHE_KEY);
    if (!raw) return [];
    const arr = JSON.parse(raw);
    return Array.isArray(arr) ? arr : [];
  } catch (e) { return []; }
}
function setRosterCache(arr) {
  localStorage.setItem(ROSTER_CACHE_KEY, JSON.stringify(arr));
}
async function fetchRoster() {
  if (!window.svendeApi || !window.svendeAuth?.getUser?.()) return getRosterCache();
  try {
    const players = await window.svendeApi.listPlayers();
    const sorted = Array.isArray(players) ? players : [];
    setRosterCache(sorted);
    return sorted;
  } catch (e) {
    console.warn("[svende] fetchRoster failed, using cache:", e.message);
    return getRosterCache();
  }
}

// React hook used by every scorekeeper's Setup screen.
function useRoster() {
  const [roster, setRoster] = useState(() => getRosterCache());
  useEffect(() => {
    let alive = true;
    const refresh = async () => {
      const next = await fetchRoster();
      if (alive) setRoster(next);
    };
    refresh();
    const unsubAuth = window.svendeAuth ? window.svendeAuth.onChange(refresh) : null;
    return () => {
      alive = false;
      if (unsubAuth) unsubAuth();
    };
  }, []);
  return roster;
}

// ---- Family context ----
// `useFamily()` is the source of truth for "which family am I in right now".
// It calls /api/families/me on auth change and caches the result so first
// paint doesn't flash an onboarding screen for users who're already members.
const FAMILY_CACHE_KEY = "svende-family-cache";
function getFamilyCache() {
  try {
    const raw = localStorage.getItem(FAMILY_CACHE_KEY);
    if (!raw) return null;
    return JSON.parse(raw);
  } catch (e) { return null; }
}
function setFamilyCache(payload) {
  if (payload) localStorage.setItem(FAMILY_CACHE_KEY, JSON.stringify(payload));
  else localStorage.removeItem(FAMILY_CACHE_KEY);
}

function useFamily() {
  const [user, setUser] = useState(() => window.svendeAuth?.getUser?.() ?? null);
  const [me, setMe] = useState(() => getFamilyCache());
  const [loading, setLoading] = useState(false);

  const refresh = async () => {
    if (!window.svendeApi || !window.svendeAuth?.getUser?.()) {
      setMe(null);
      setFamilyCache(null);
      return null;
    }
    setLoading(true);
    try {
      const data = await window.svendeApi.me();
      setMe(data);
      setFamilyCache(data);
      return data;
    } catch (e) {
      console.warn("[svende] /api/families/me failed:", e.message);
      return null;
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (!window.svendeAuth) return;
    const unsub = window.svendeAuth.onChange((u) => {
      setUser(u);
      if (u) refresh();
      else { setMe(null); setFamilyCache(null); }
    });
    if (window.svendeAuth.getUser?.()) refresh();
    return unsub;
  }, []);

  const currentFamily = me?.families?.[0] ?? null;
  return { user, me, currentFamily, loading, refresh };
}

// Onboarding screen — shown when a signed-in user has zero families.
function FamilyOnboarding({ onJoined }) {
  const [mode, setMode] = useState("choose"); // "choose" | "create" | "join"
  const [name, setName] = useState("");
  const [code, setCode] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");

  const create = async () => {
    if (!name.trim() || busy) return;
    setBusy(true); setError("");
    try {
      await window.svendeApi.createFamily({ name: name.trim() });
      await onJoined();
    } catch (e) { setError(e.message || "Could not create family"); }
    finally { setBusy(false); }
  };

  const join = async () => {
    const cleaned = code.toUpperCase().replace(/[^A-Z0-9]/g, "");
    if (cleaned.length !== 6 || busy) return;
    setBusy(true); setError("");
    try {
      await window.svendeApi.redeemInvite(cleaned);
      await onJoined();
    } catch (e) { setError(e.message || "Could not redeem invite"); }
    finally { setBusy(false); }
  };

  return (
    <div style={{
      minHeight: "100vh", background: "var(--bg)", color: "var(--ink)",
      position: "relative", display: "flex", alignItems: "center", justifyContent: "center",
      padding: "40px 24px",
    }}>
      <ConfettiBg count={32} seed={101} />
      <div style={{
        position: "relative", zIndex: 1,
        maxWidth: 540, width: "100%",
        background: "rgba(255,255,255,0.04)",
        border: "3px solid var(--border)",
        borderRadius: "var(--radius)",
        boxShadow: "var(--card-shadow)",
        padding: 36, textAlign: "center",
      }}>
        <div style={{ display: "flex", justifyContent: "center", marginBottom: 16 }}>
          <Mascot size={64} />
        </div>
        <div style={{
          fontFamily: "'DM Sans', sans-serif", fontSize: 13,
          textTransform: "uppercase", letterSpacing: "0.18em",
          color: "var(--yellow)", fontWeight: 700, marginBottom: 8,
        }}>— Welcome to SvendeBanan</div>
        <h1 style={{
          fontFamily: "'Space Grotesk', sans-serif",
          fontSize: 36, fontWeight: 700, margin: "0 0 12px",
          letterSpacing: "-0.03em",
        }}>Set up your family</h1>
        <p style={{
          fontFamily: "'DM Sans', sans-serif", fontSize: 15,
          color: "var(--ink-dim)", margin: "0 0 28px", textWrap: "pretty",
        }}>Every family has its own private leaderboard, roster, and game history.</p>

        {mode === "choose" && (
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
            <button onClick={() => setMode("create")} style={onbCardStyle("var(--pink)")}>
              <span style={{ fontSize: 32 }}>👨‍👩‍👧‍👦</span>
              <span>Create a new family</span>
              <span style={onbCardSubStyle}>Start fresh, you'll be the owner</span>
            </button>
            <button onClick={() => setMode("join")} style={onbCardStyle("var(--teal)")}>
              <span style={{ fontSize: 32 }}>🎟️</span>
              <span>Join an existing family</span>
              <span style={onbCardSubStyle}>Use a 6-letter invite code</span>
            </button>
          </div>
        )}

        {mode === "create" && (
          <div style={{ textAlign: "left" }}>
            <label style={onbLabelStyle}>Family name</label>
            <input
              autoFocus
              value={name}
              onChange={(e) => setName(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && create()}
              placeholder="The Heltmølgaards"
              style={onbInputStyle}
            />
            {error && <div style={onbErrorStyle}>{error}</div>}
            <div style={{ display: "flex", gap: 10, marginTop: 16 }}>
              <button onClick={() => setMode("choose")} style={onbBtnStyleSecondary}>← Back</button>
              <button onClick={create} disabled={!name.trim() || busy} style={onbBtnStyle("var(--lime)", !name.trim() || busy)}>
                {busy ? "Creating…" : "Create family →"}
              </button>
            </div>
          </div>
        )}

        {mode === "join" && (
          <div style={{ textAlign: "left" }}>
            <label style={onbLabelStyle}>Invite code</label>
            <input
              autoFocus
              value={code}
              onChange={(e) => setCode(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 6))}
              onKeyDown={(e) => e.key === "Enter" && join()}
              placeholder="K7M3PA"
              style={{ ...onbInputStyle, fontFamily: "'Space Grotesk', monospace", letterSpacing: "0.2em", textAlign: "center", fontSize: 24, fontWeight: 700 }}
            />
            {error && <div style={onbErrorStyle}>{error}</div>}
            <div style={{ display: "flex", gap: 10, marginTop: 16 }}>
              <button onClick={() => setMode("choose")} style={onbBtnStyleSecondary}>← Back</button>
              <button onClick={join} disabled={code.length !== 6 || busy} style={onbBtnStyle("var(--teal)", code.length !== 6 || busy)}>
                {busy ? "Joining…" : "Join family →"}
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

const onbCardStyle = (accent) => ({
  background: accent, color: "#0a0a0a",
  border: "3px solid var(--border)", borderRadius: 18,
  padding: 24,
  display: "flex", flexDirection: "column", gap: 8, alignItems: "center",
  fontFamily: "'Space Grotesk', sans-serif", fontWeight: 700, fontSize: 17,
  boxShadow: "5px 5px 0 var(--border)",
  cursor: "pointer", transition: "all 0.15s",
});
const onbCardSubStyle = {
  fontFamily: "'DM Sans', sans-serif", fontSize: 12, fontWeight: 500, opacity: 0.75,
};
const onbLabelStyle = {
  display: "block",
  fontFamily: "'DM Sans', sans-serif", fontSize: 12,
  textTransform: "uppercase", letterSpacing: "0.15em",
  color: "var(--ink-dim)", fontWeight: 700, marginBottom: 8,
};
const onbInputStyle = {
  width: "100%", boxSizing: "border-box",
  background: "rgba(255,255,255,0.06)", color: "var(--ink)",
  border: "2px solid rgba(255,255,255,0.15)", borderRadius: 14,
  padding: "14px 16px", fontFamily: "'DM Sans', sans-serif", fontSize: 16,
  outline: "none",
};
const onbErrorStyle = {
  marginTop: 12,
  fontFamily: "'DM Sans', sans-serif", fontSize: 13,
  color: "var(--coral)", fontWeight: 600,
};
const onbBtnStyle = (accent, disabled) => ({
  flex: 1,
  background: disabled ? "rgba(255,255,255,0.1)" : accent,
  color: disabled ? "var(--ink-dim)" : "#0a0a0a",
  border: "3px solid var(--border)", borderRadius: 14,
  padding: "14px 18px",
  fontFamily: "'Space Grotesk', sans-serif", fontWeight: 700, fontSize: 15,
  cursor: disabled ? "not-allowed" : "pointer",
  boxShadow: disabled ? "none" : "5px 5px 0 var(--border)",
});
const onbBtnStyleSecondary = {
  background: "transparent", color: "var(--ink)",
  border: "2px solid var(--border)", borderRadius: 14,
  padding: "14px 18px",
  fontFamily: "'Space Grotesk', sans-serif", fontWeight: 600, fontSize: 14, cursor: "pointer",
};

// Invite modal — shown via the "Invite" button in the header.
function InviteModal({ family, onClose }) {
  const [invites, setInvites] = useState([]);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");

  const refresh = async () => {
    try {
      const list = await window.svendeApi.listInvites(family.id);
      setInvites(list);
    } catch (e) { setError(e.message); }
  };
  useEffect(() => { refresh(); }, [family.id]);

  const generate = async () => {
    setBusy(true); setError("");
    try {
      await window.svendeApi.createInvite(family.id);
      await refresh();
    } catch (e) { setError(e.message); }
    finally { setBusy(false); }
  };

  const revoke = async (code) => {
    if (!confirm(`Revoke invite ${code}? Anyone using it will get an error.`)) return;
    await window.svendeApi.revokeInvite(family.id, code);
    await refresh();
  };

  const copy = async (code) => {
    try { await navigator.clipboard.writeText(code); }
    catch (e) { /* ignore */ }
  };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 100,
      background: "rgba(10,10,30,0.7)",
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: 24, backdropFilter: "blur(6px)",
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        maxWidth: 480, width: "100%",
        background: "var(--bg)", color: "var(--ink)",
        border: "3px solid var(--border)", borderRadius: "var(--radius)",
        boxShadow: "var(--card-shadow)", padding: 28,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 6 }}>
          <h2 style={{ fontFamily: "'Space Grotesk', sans-serif", fontSize: 24, fontWeight: 700, margin: 0 }}>
            Invite to {family.name}
          </h2>
          <button onClick={onClose} style={{
            background: "transparent", color: "var(--ink-dim)",
            border: "none", fontSize: 20, cursor: "pointer", padding: 0, lineHeight: 1,
          }}>✕</button>
        </div>
        <p style={{ fontFamily: "'DM Sans', sans-serif", fontSize: 14, color: "var(--ink-dim)", margin: "0 0 20px" }}>
          Share a code with a family member. Codes expire after 24 hours and can be used by multiple people.
        </p>

        {invites.length === 0 ? (
          <div style={{
            textAlign: "center", padding: "32px 0",
            border: "2px dashed rgba(255,255,255,0.2)",
            borderRadius: 14, marginBottom: 16,
          }}>
            <div style={{ fontFamily: "'DM Sans', sans-serif", color: "var(--ink-dim)", marginBottom: 12 }}>
              No active invites
            </div>
            <button onClick={generate} disabled={busy} style={onbBtnStyle("var(--yellow)", busy)}>
              {busy ? "Generating…" : "Generate invite code"}
            </button>
          </div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 10, marginBottom: 16 }}>
            {invites.map((inv) => {
              const expiresIn = new Date(inv.expiresAt).getTime() - Date.now();
              const hours = Math.max(0, Math.round(expiresIn / 3600000));
              return (
                <div key={inv.code} style={{
                  display: "flex", alignItems: "center", gap: 12,
                  padding: "12px 14px",
                  background: "rgba(255,255,255,0.04)",
                  border: "2px solid var(--border)",
                  borderRadius: 14,
                }}>
                  <div style={{
                    fontFamily: "'Space Grotesk', monospace",
                    fontSize: 22, fontWeight: 800, letterSpacing: "0.2em",
                    color: "var(--yellow)", flex: 1,
                  }}>{inv.code}</div>
                  <div style={{
                    fontFamily: "'DM Sans', sans-serif", fontSize: 12,
                    color: "var(--ink-dim)",
                  }}>{hours}h left</div>
                  <button onClick={() => copy(inv.code)} title="Copy" style={inviteIconBtn}>📋</button>
                  <button onClick={() => revoke(inv.code)} title="Revoke" style={inviteIconBtn}>🗑</button>
                </div>
              );
            })}
            <button onClick={generate} disabled={busy} style={{
              ...onbBtnStyleSecondary,
              padding: "10px 18px",
            }}>{busy ? "Generating…" : "+ Generate another"}</button>
          </div>
        )}
        {error && <div style={onbErrorStyle}>{error}</div>}
      </div>
    </div>
  );
}

const inviteIconBtn = {
  background: "transparent", border: "none",
  cursor: "pointer", padding: 4, fontSize: 16, opacity: 0.7,
};

// ---- Game history ----
// Source of truth is the API (/api/games) so the family-wide leaderboard is
// shared across devices. localStorage is kept as a write-ahead cache for
// instant first paint and for offline resilience: writes go to the cache
// immediately, then sync to the server in the background. On reads we serve
// from the cache, refresh from the API, and overwrite the cache with the
// authoritative server response.
const HISTORY_KEY = "svende-history";
const PENDING_KEY = "svende-history-pending";
const GAME_TITLES = {
  "danish-500": "500",
  "backgammon": "Backgammon",
  "shithead": "Shithead",
};

function getHistoryCache() {
  try {
    const raw = localStorage.getItem(HISTORY_KEY);
    if (!raw) return [];
    const arr = JSON.parse(raw);
    return Array.isArray(arr) ? arr : [];
  } catch (e) { return []; }
}

function setHistoryCache(arr) {
  localStorage.setItem(HISTORY_KEY, JSON.stringify(arr));
}

// Synchronous read for first-paint (returns cached snapshot).
function getHistory() {
  return getHistoryCache();
}

// Async read that hits the API and refreshes the cache. Falls back to the
// cached snapshot on any failure (offline, signed-out, server down).
async function fetchHistory() {
  if (!window.svendeApi || !window.svendeAuth) return getHistoryCache();
  const user = window.svendeAuth.getUser ? window.svendeAuth.getUser() : null;
  if (!user) return getHistoryCache();
  try {
    const games = await window.svendeApi.listGames();
    const sorted = Array.isArray(games) ? [...games].sort((a, b) =>
      (a.finishedAt || "").localeCompare(b.finishedAt || "")
    ) : [];
    setHistoryCache(sorted);
    return sorted;
  } catch (e) {
    console.warn("[svende] fetchHistory failed, using cache:", e.message);
    return getHistoryCache();
  }
}

function setHistory(arr) {
  // Used by the "Reset history" button. Wipe local cache + tell server.
  setHistoryCache(arr);
  if (arr.length === 0 && window.svendeApi) {
    window.svendeApi.deleteAllGames().catch((e) =>
      console.warn("[svende] deleteAllGames failed:", e.message),
    );
  }
}

// Pending queue: writes that failed to reach the server. Drained on next
// sync attempt (auth state change, focus, online event).
function getPendingQueue() {
  try {
    const raw = localStorage.getItem(PENDING_KEY);
    return raw ? JSON.parse(raw) : [];
  } catch (e) { return []; }
}

function setPendingQueue(arr) {
  localStorage.setItem(PENDING_KEY, JSON.stringify(arr));
}

async function flushPending() {
  if (!window.svendeApi) return;
  const user = window.svendeAuth?.getUser?.();
  if (!user) return;
  const queue = getPendingQueue();
  if (queue.length === 0) return;
  const remaining = [];
  for (const record of queue) {
    try {
      await window.svendeApi.saveGame(record);
    } catch (e) {
      console.warn("[svende] flushPending: keeping in queue:", e.message);
      remaining.push(record);
    }
  }
  setPendingQueue(remaining);
}

function appendGame(record) {
  if (!record || !record.id) return;
  if (!record.finishedAt) record.finishedAt = new Date().toISOString();
  // 1. Update local cache immediately (so the hub feels instant).
  const h = getHistoryCache();
  const idx = h.findIndex(g => g.id === record.id);
  if (idx >= 0) h[idx] = record; else h.push(record);
  setHistoryCache(h);
  // 2. Push to the API. If it fails, queue for retry.
  if (window.svendeApi && window.svendeAuth?.getUser?.()) {
    window.svendeApi.saveGame(record).catch((e) => {
      console.warn("[svende] saveGame failed, queuing:", e.message);
      const q = getPendingQueue();
      const pIdx = q.findIndex((r) => r.id === record.id);
      if (pIdx >= 0) q[pIdx] = record; else q.push(record);
      setPendingQueue(q);
    });
  } else {
    // Not signed in / API unavailable — queue and let auth flow drain it.
    const q = getPendingQueue();
    const pIdx = q.findIndex((r) => r.id === record.id);
    if (pIdx >= 0) q[pIdx] = record; else q.push(record);
    setPendingQueue(q);
  }
}

// Drain the queue whenever the user signs in or comes back online.
if (typeof window !== "undefined") {
  if (window.svendeAuth) {
    window.svendeAuth.onChange((user) => { if (user) flushPending(); });
  }
  window.addEventListener("online", flushPending);
}

function newGameId() {
  return Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8);
}

function getPlayerStats(history) {
  const stats = {};
  for (const g of history) {
    g.players.forEach((p, i) => {
      if (!stats[p.name]) stats[p.name] = {
        name: p.name, color: p.color,
        played: 0, wins: 0, points: 0, lastPlayed: null,
      };
      stats[p.name].played += 1;
      if (g.winner && g.winner.name === p.name) stats[p.name].wins += 1;
      const sc = (g.finalScores && g.finalScores[i]) || 0;
      stats[p.name].points += sc;
      if (!stats[p.name].lastPlayed || g.finishedAt > stats[p.name].lastPlayed) {
        stats[p.name].lastPlayed = g.finishedAt;
      }
      // keep latest known color
      if (p.color) stats[p.name].color = p.color;
    });
  }
  return stats;
}

function lastPlayedByGame(history) {
  const out = {};
  for (const g of history) {
    if (!out[g.game] || g.finishedAt > out[g.game]) out[g.game] = g.finishedAt;
  }
  return out;
}

function formatRelativeTime(iso) {
  if (!iso) return "Never played";
  const ts = new Date(iso).getTime();
  if (isNaN(ts)) return "Never played";
  const diff = Date.now() - ts;
  if (diff < 0) return "just now";
  const m = Math.floor(diff / 60000);
  if (m < 1) return "just now";
  if (m < 60) return `${m} minute${m === 1 ? "" : "s"} ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h} hour${h === 1 ? "" : "s"} ago`;
  const d = Math.floor(h / 24);
  if (d < 7) return `${d} day${d === 1 ? "" : "s"} ago`;
  const w = Math.floor(d / 7);
  if (w < 4) return `${w} week${w === 1 ? "" : "s"} ago`;
  const mo = Math.floor(d / 30);
  if (mo < 12) return `${mo} month${mo === 1 ? "" : "s"} ago`;
  const y = Math.floor(d / 365);
  return `${y} year${y === 1 ? "" : "s"} ago`;
}

// Player avatar — simple colored circle with initial
function Avatar({ name, color, size = 48, ring = false }) {
  const initial = (name || "?")[0].toUpperCase();
  return (
    <div
      style={{
        width: size,
        height: size,
        borderRadius: "50%",
        background: color,
        color: "#0a0a0a",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        fontFamily: "'Space Grotesk', sans-serif",
        fontWeight: 700,
        fontSize: size * 0.42,
        border: "2px solid var(--border)",
        boxShadow: ring ? "0 0 0 4px var(--yellow), 4px 4px 0 var(--border)" : "3px 3px 0 var(--border)",
        flexShrink: 0,
      }}
    >
      {initial}
    </div>
  );
}

Object.assign(window, {
  THEMES,
  applyTheme,
  Mascot,
  ConfettiBg,
  Logo,
  Header,
  AuthChip,
  Pill,
  Avatar,
  PLAYER_COLORS,
  pickRandomPlayerColor,
  fetchRoster,
  useRoster,
  useFamily,
  FamilyOnboarding,
  InviteModal,
  GAME_TITLES,
  HISTORY_KEY,
  getHistory,
  fetchHistory,
  setHistory,
  appendGame,
  flushPending,
  newGameId,
  getPlayerStats,
  lastPlayedByGame,
  formatRelativeTime,
});
