/* Play screen — Beat the Models.
   Sign in with a one-time link, claim a handle, then predict. Picks can be
   entered for any upcoming matchday and edited freely until each individual
   match kicks off; the database enforces that lock, not this screen. */

function ScoreBox({ value, onChange, disabled, label }) {
  return (
    <input
      type="number" min="0" max="99" inputMode="numeric"
      aria-label={label}
      value={value == null ? "" : value}
      disabled={disabled}
      onChange={(e) => {
        const v = e.target.value;
        if (v === "") return onChange(null);
        const n = Math.max(0, Math.min(99, parseInt(v, 10)));
        onChange(Number.isNaN(n) ? null : n);
      }}
      style={{
        width: 44, padding: "6px 4px", textAlign: "center",
        fontFamily: "var(--font-mono)", fontSize: 15,
        background: "var(--bg-app)", color: "var(--text-primary)",
        border: "1px solid var(--border)", borderRadius: "var(--radius-md)",
        opacity: disabled ? 0.5 : 1,
      }}
    />
  );
}

/* Probabilities are entered as whole percentages — people think in "45 / 30 / 25",
   not 0.45 — and converted to 0-1 on save to match how the models store them. */
function ProbBox({ value, onChange, tone, label }) {
  return (
    <input
      type="number" min="0" max="100" inputMode="numeric"
      aria-label={label} placeholder="–"
      value={value == null ? "" : value}
      onChange={(e) => {
        const v = e.target.value;
        if (v === "") return onChange(null);
        const n = Math.max(0, Math.min(100, parseInt(v, 10)));
        onChange(Number.isNaN(n) ? null : n);
      }}
      style={{
        width: 40, padding: "4px 3px", textAlign: "center",
        fontFamily: "var(--font-mono)", fontSize: 12,
        background: "var(--bg-app)",
        color: tone === "bad" ? "var(--negative)" : "var(--text-secondary)",
        border: `1px solid ${tone === "bad" ? "var(--negative)" : "var(--border)"}`,
        borderRadius: "var(--radius-md)",
      }}
    />
  );
}

// none = left blank (fine, scoreline only) · ok = all three, summing to 100
// · bad = partly filled or not summing, which blocks the save rather than
// silently discarding numbers the player typed.
function probState(p) {
  const vals = [p.ph, p.pd, p.pa];
  const filled = vals.filter((v) => v != null && v !== "");
  if (filled.length === 0) return "none";
  if (filled.length < 3) return "bad";
  const sum = vals.reduce((a, b) => a + Number(b), 0);
  return Math.abs(sum - 100) <= 1 ? "ok" : "bad";
}

function PlayScreen() {
  const { Card, Eyebrow, Badge, Stat } = window.TheOracleLeagueDesignSystem_92fa71;
  const Auth = window.OracleAuth;
  const mobile = window.useMobile();
  const comp = window.COMPETITION || {};

  const [auth, setAuth] = React.useState(Auth ? Auth.state : { ready: false });
  React.useEffect(() => (Auth ? Auth.onChange(setAuth) : undefined), []);

  const [email, setEmail] = React.useState("");
  const [sent, setSent] = React.useState(false);
  const [handle, setHandle] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [msg, setMsg] = React.useState(null);

  // Only unstarted matches can be predicted; kickoff is the lock.
  const rounds = React.useMemo(() => {
    const fx = ((window.MATCHDAY && window.MATCHDAY.fixtures) || [])
      .filter((f) => f.id !== "__placeholder__" && f.status === "upcoming" && f.matchday);
    const by = new Map();
    fx.forEach((f) => { if (!by.has(f.matchday)) by.set(f.matchday, []); by.get(f.matchday).push(f); });
    return [...by.entries()].sort((a, b) => a[0] - b[0]).map(([n, fixtures]) => ({ n, fixtures }));
  }, [auth.session]);

  const [round, setRound] = React.useState(null);
  const active = rounds.find((r) => r.n === round) || rounds[0] || null;

  const [picks, setPicks] = React.useState({});
  const [saved, setSaved] = React.useState({});
  const [showProbs, setShowProbs] = React.useState(false);
  const signedIn = !!(auth.session && auth.profile);

  const pct = (v) => (v == null ? null : Math.round(Number(v) * 100));

  // Load existing picks for the visible matchday so edits start from what is stored.
  React.useEffect(() => {
    if (!signedIn || !active) return;
    let cancelled = false;
    Auth.myPredictions(active.fixtures.map((f) => f.id)).then((existing) => {
      if (cancelled) return;
      const next = {};
      let anyProbs = false;
      Object.keys(existing).forEach((mid) => {
        const e = existing[mid];
        if (e.prob_home != null) anyProbs = true;
        next[mid] = {
          home: e.home_score, away: e.away_score,
          ph: pct(e.prob_home), pd: pct(e.prob_draw), pa: pct(e.prob_away),
        };
      });
      setSaved(next);
      setPicks(next);
      // Someone who has used probabilities before shouldn't have to re-open the panel.
      if (anyProbs) setShowProbs(true);
    });
    return () => { cancelled = true; };
  }, [signedIn, active && active.n]);

  const setPick = (mid, field, v) =>
    setPicks((p) => ({ ...p, [mid]: { ...(p[mid] || {}), [field]: v } }));

  const same = (a, b) => (a == null ? null : Number(a)) === (b == null ? null : Number(b));

  const complete = active
    ? active.fixtures.filter((f) => picks[f.id] && picks[f.id].home != null && picks[f.id].away != null)
    : [];
  const dirty = complete.filter((f) => {
    const s = saved[f.id], p = picks[f.id];
    if (!s) return true;
    return !same(s.home, p.home) || !same(s.away, p.away)
      || !same(s.ph, p.ph) || !same(s.pd, p.pd) || !same(s.pa, p.pa);
  });
  const invalid = dirty.filter((f) => probState(picks[f.id]) === "bad");
  const canSave = dirty.length > 0 && invalid.length === 0 && !busy;

  const save = () => {
    if (!canSave) return;
    setBusy(true); setMsg(null);
    Auth.savePredictions(dirty.map((f) => {
      const p = picks[f.id];
      const ok = probState(p) === "ok";
      return {
        match_id: f.id, home_score: p.home, away_score: p.away,
        prob_home: ok ? Number(p.ph) / 100 : null,
        prob_draw: ok ? Number(p.pd) / 100 : null,
        prob_away: ok ? Number(p.pa) / 100 : null,
      };
    }))
      .then((n) => {
        setSaved((s) => {
          const next = { ...s };
          dirty.forEach((f) => { next[f.id] = { ...picks[f.id] }; });
          return next;
        });
        setMsg({ tone: "ok", text: `Saved ${n} pick${n === 1 ? "" : "s"}.` });
      })
      .catch((e) => setMsg({ tone: "err", text: e.message || "Could not save." }))
      .finally(() => setBusy(false));
  };

  const heading = (
    <div style={{ display: "flex", flexDirection: "column", gap: 12, marginBottom: 20 }}>
      <Eyebrow tone="gold">Beat the models</Eyebrow>
      <h1 style={{
        fontFamily: "var(--font-display)", fontSize: mobile ? 26 : 38, fontWeight: 400,
        letterSpacing: "-0.02em", color: "var(--text-primary)", lineHeight: 1.12, margin: 0,
      }}>
        Think you can do better?
      </h1>
    </div>
  );

  if (!Auth || !auth.ready) {
    return <div style={{ padding: mobile ? "16px" : "28px 32px", maxWidth: 720 }}>{heading}
      <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-muted)" }}>Loading…</span>
    </div>;
  }

  // ── Signed out ──────────────────────────────────────────────────────────
  if (!auth.session) {
    return (
      <div style={{ padding: mobile ? "16px" : "28px 32px", maxWidth: 640 }}>
        {heading}
        <p style={{
          fontFamily: "var(--font-sans)", fontSize: 15, color: "var(--text-secondary)",
          lineHeight: 1.55, margin: "0 0 20px",
        }}>
          At the World Cup every single model out-predicted the one human who played — nine models,
          nine defeats. Enter your own scorelines for {comp.name || "the season"}, and you are scored
          on exactly the same rules the models are. Predictions lock when each match kicks off.
        </p>
        <Card padding={18}>
          <Eyebrow rule>Sign in</Eyebrow>
          {sent ? (
            <div style={{ marginTop: 12 }}>
              <Badge tone="gold">Check your email</Badge>
              <p style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-secondary)", lineHeight: 1.5, margin: "10px 0 0" }}>
                A one-time sign-in link is on its way to <strong>{email}</strong>. No password needed.
              </p>
            </div>
          ) : (
            <form onSubmit={(e) => {
              e.preventDefault();
              if (!email.trim()) return;
              setBusy(true); setMsg(null);
              Auth.signIn(email.trim())
                .then(() => setSent(true))
                .catch((err) => setMsg({ tone: "err", text: err.message || "Could not send the link." }))
                .finally(() => setBusy(false));
            }} style={{ marginTop: 12, display: "flex", gap: 8, flexWrap: "wrap" }}>
              <input
                type="email" required placeholder="you@example.com"
                value={email} onChange={(e) => setEmail(e.target.value)}
                style={{
                  flex: 1, minWidth: 200, padding: "9px 12px",
                  fontFamily: "var(--font-sans)", fontSize: 14,
                  background: "var(--bg-app)", color: "var(--text-primary)",
                  border: "1px solid var(--border)", borderRadius: "var(--radius-md)",
                }}
              />
              <button type="submit" disabled={busy} style={{
                padding: "9px 18px", borderRadius: "var(--radius-md)", border: "none",
                background: "var(--accent)", color: "var(--text-on-accent)",
                fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 700,
                cursor: busy ? "default" : "pointer", opacity: busy ? 0.6 : 1,
              }}>{busy ? "Sending…" : "Send link"}</button>
            </form>
          )}
          {msg && (
            <p style={{
              fontFamily: "var(--font-sans)", fontSize: 13, margin: "10px 0 0",
              color: msg.tone === "err" ? "var(--negative)" : "var(--positive)",
            }}>{msg.text}</p>
          )}
        </Card>
      </div>
    );
  }

  // ── Signed in, no handle yet ────────────────────────────────────────────
  if (!auth.profile) {
    return (
      <div style={{ padding: mobile ? "16px" : "28px 32px", maxWidth: 560 }}>
        {heading}
        <Card padding={18}>
          <Eyebrow rule>Choose a handle</Eyebrow>
          <p style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-secondary)", lineHeight: 1.5, margin: "10px 0 12px" }}>
            This is the name that appears on the leaderboard next to the models.
          </p>
          <form onSubmit={(e) => {
            e.preventDefault();
            setBusy(true); setMsg(null);
            Auth.saveHandle(handle)
              .catch((err) => setMsg({ tone: "err", text: err.message }))
              .finally(() => setBusy(false));
          }} style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <input
              required placeholder="your_handle" value={handle}
              onChange={(e) => setHandle(e.target.value)}
              style={{
                flex: 1, minWidth: 180, padding: "9px 12px",
                fontFamily: "var(--font-mono)", fontSize: 14,
                background: "var(--bg-app)", color: "var(--text-primary)",
                border: "1px solid var(--border)", borderRadius: "var(--radius-md)",
              }}
            />
            <button type="submit" disabled={busy} style={{
              padding: "9px 18px", borderRadius: "var(--radius-md)", border: "none",
              background: "var(--accent)", color: "var(--text-on-accent)",
              fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 700,
              cursor: busy ? "default" : "pointer", opacity: busy ? 0.6 : 1,
            }}>Claim</button>
          </form>
          {msg && <p style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--negative)", margin: "10px 0 0" }}>{msg.text}</p>}
        </Card>
      </div>
    );
  }

  // ── Signed in and playing ───────────────────────────────────────────────
  return (
    <div style={{ padding: mobile ? "16px" : "28px 32px", maxWidth: 820 }}>
      <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 12, marginBottom: 18, flexWrap: "wrap" }}>
        <div>
          <Eyebrow tone="gold">Beat the models</Eyebrow>
          <h1 style={{
            fontFamily: "var(--font-display)", fontSize: mobile ? 24 : 34, fontWeight: 400,
            letterSpacing: "-0.02em", color: "var(--text-primary)", lineHeight: 1.1, margin: "10px 0 0",
          }}>
            Your picks, <span style={{ fontFamily: "var(--font-mono)", fontSize: mobile ? 20 : 28 }}>{auth.profile.handle}</span>
          </h1>
        </div>
        <button onClick={() => Auth.signOut()} style={{
          padding: "6px 12px", borderRadius: "var(--radius-md)", cursor: "pointer",
          border: "1px solid var(--border)", background: "transparent",
          color: "var(--text-muted)", fontFamily: "var(--font-mono)", fontSize: 11.5,
        }}>Sign out</button>
      </div>

      {(() => {
        // Your own standing, once any of your matches has been played.
        const H = window.HUMANS || { rows: [], fieldPtsPerMatch: null };
        const me = H.rows.filter((r) => r.userId === auth.session.user.id)[0];
        if (!me || !me.matches) return null;
        const field = H.fieldPtsPerMatch;
        const ahead = field != null && me.ptsPerMatch > field;
        const rank = H.rows.filter((r) => r.matches > 0 && (r.ptsPerMatch || 0) > (me.ptsPerMatch || 0)).length + 1;
        return (
          <Card padding={16} style={{ marginBottom: 16 }}>
            <Eyebrow rule>Your season</Eyebrow>
            <div style={{
              display: "grid", gridTemplateColumns: mobile ? "1fr 1fr" : "repeat(4, 1fr)",
              gap: 14, marginTop: 12,
            }}>
              <Stat label="Among humans" value={`#${rank}`} size="sm" />
              <Stat label="Points / match" value={me.ptsPerMatch == null ? "—" : me.ptsPerMatch.toFixed(2)} size="sm" />
              <Stat label="Matches" value={me.matches} size="sm" />
              <Stat label="vs the models"
                value={me.vsField == null ? "—" : (me.vsField > 0 ? "+" : "") + me.vsField.toFixed(2)} size="sm" />
            </div>
            {field != null && (
              <p style={{
                fontFamily: "var(--font-sans)", fontSize: 12.5, lineHeight: 1.5,
                color: ahead ? "var(--positive)" : "var(--text-muted)", margin: "12px 0 0",
              }}>
                {ahead
                  ? `You are outscoring the models, who average ${field.toFixed(2)} points a match.`
                  : `The models average ${field.toFixed(2)} points a match. Still to catch.`}
              </p>
            )}
          </Card>
        );
      })()}

      {!active ? (
        <Card padding={20}>
          <Badge tone="neutral">Nothing to predict</Badge>
          <p style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-secondary)", lineHeight: 1.5, margin: "10px 0 0" }}>
            Every remaining fixture has already kicked off. New matchdays open here as soon as they are scheduled.
          </p>
        </Card>
      ) : (
        <React.Fragment>
          <div style={{ display: "flex", gap: 5, overflowX: "auto", padding: "0 0 12px" }}>
            {rounds.map((r) => {
              const on = active.n === r.n;
              return (
                <button key={r.n} onClick={() => setRound(r.n)} style={{
                  flex: "none", minWidth: 32, padding: "5px 9px", borderRadius: "var(--radius-md)",
                  cursor: "pointer", fontFamily: "var(--font-mono)", fontSize: 11.5,
                  border: `1px solid ${on ? "var(--accent)" : "var(--border)"}`,
                  background: on ? "var(--accent-soft)" : "transparent",
                  color: on ? "var(--gold-200)" : "var(--text-secondary)",
                  fontWeight: on ? 700 : 500,
                }}>{r.n}</button>
              );
            })}
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 12, flexWrap: "wrap" }}>
            <span style={{ fontFamily: "var(--font-sans)", fontSize: 15, fontWeight: 600, color: "var(--text-primary)" }}>
              Matchday {String(active.n).padStart(2, "0")}
            </span>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-faint)" }}>
              {complete.length}/{active.fixtures.length} filled
            </span>
            <label style={{
              marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 7,
              cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 12.5,
              color: "var(--text-secondary)",
            }}>
              <input type="checkbox" checked={showProbs}
                onChange={(e) => setShowProbs(e.target.checked)}
                style={{ accentColor: "var(--accent)", width: 14, height: 14 }} />
              Add win probabilities
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--text-faint)" }}>optional</span>
            </label>
          </div>

          {showProbs && (
            <div style={{
              padding: "10px 12px", marginBottom: 12, borderRadius: "var(--radius-md)",
              border: "1px solid var(--border)", background: "var(--bg-surface)",
              fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--text-muted)", lineHeight: 1.5,
            }}>
              Percentages for home / draw / away, adding up to 100. Leave a match blank to be
              scored on the scoreline alone — filling them in also scores you on calibration
              (Brier), the same measure the models are judged on.
            </div>
          )}

          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            {active.fixtures.map((f) => {
              const p = picks[f.id] || {};
              const isSaved = saved[f.id] && saved[f.id].home === p.home && saved[f.id].away === p.away;
              return (
                <Card key={f.id} padding={mobile ? 12 : 14}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                    <span style={{
                      fontFamily: "var(--font-sans)", fontSize: mobile ? 13 : 14, color: "var(--text-primary)",
                      flex: 1, minWidth: 0, textAlign: "right",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                    }}>{f.home.name}</span>
                    <ScoreBox label={`${f.home.name} goals`} value={p.home}
                      onChange={(v) => setPick(f.id, "home", v)} />
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-faint)" }}>v</span>
                    <ScoreBox label={`${f.away.name} goals`} value={p.away}
                      onChange={(v) => setPick(f.id, "away", v)} />
                    <span style={{
                      fontFamily: "var(--font-sans)", fontSize: mobile ? 13 : 14, color: "var(--text-primary)",
                      flex: 1, minWidth: 0,
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                    }}>{f.away.name}</span>
                    {!mobile && (
                      <span style={{ width: 16, flex: "none", textAlign: "center", fontFamily: "var(--font-mono)", fontSize: 12, color: isSaved ? "var(--positive)" : "var(--text-faint)" }}>
                        {isSaved ? "✓" : ""}
                      </span>
                    )}
                  </div>
                  {showProbs && (() => {
                    const st = probState(p);
                    const sum = [p.ph, p.pd, p.pa].reduce((a, b) => a + (b == null ? 0 : Number(b)), 0);
                    return (
                      <div style={{
                        display: "flex", alignItems: "center", justifyContent: "center",
                        gap: 8, marginTop: 10, paddingTop: 10, borderTop: "1px solid var(--border)",
                        flexWrap: "wrap",
                      }}>
                        <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--text-faint)" }}>win %</span>
                        <ProbBox label="home win %" value={p.ph} tone={st === "bad" ? "bad" : null}
                          onChange={(v) => setPick(f.id, "ph", v)} />
                        <ProbBox label="draw %" value={p.pd} tone={st === "bad" ? "bad" : null}
                          onChange={(v) => setPick(f.id, "pd", v)} />
                        <ProbBox label="away win %" value={p.pa} tone={st === "bad" ? "bad" : null}
                          onChange={(v) => setPick(f.id, "pa", v)} />
                        <span style={{
                          fontFamily: "var(--font-mono)", fontSize: 10.5, minWidth: 66,
                          color: st === "bad" ? "var(--negative)" : st === "ok" ? "var(--positive)" : "var(--text-faint)",
                        }}>
                          {st === "none" ? "optional" : st === "ok" ? "= 100 ✓" : `= ${sum}`}
                        </span>
                      </div>
                    );
                  })()}
                  <div style={{ marginTop: 6, fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--text-faint)", textAlign: "center" }}>
                    locks at kickoff · {f.kickoff ? new Date(f.kickoff).toLocaleString() : "TBC"}
                  </div>
                </Card>
              );
            })}
          </div>

          {/* Sticky save bar. Filling in ten fixtures leaves you at the bottom of
              the page, so the control has to travel with you rather than sit at
              the top where it started. */}
          <div style={{
            position: "sticky", bottom: mobile ? 68 : 12, zIndex: 15,
            marginTop: 16, padding: "10px 14px",
            display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
            borderRadius: "var(--radius-lg)", border: "1px solid var(--border)",
            background: "color-mix(in srgb, var(--bg-raised) 92%, transparent)",
            backdropFilter: "blur(10px)", boxShadow: "0 6px 24px rgba(0,0,0,0.35)",
          }}>
            <span style={{
              fontFamily: "var(--font-sans)", fontSize: 12.5,
              color: invalid.length ? "var(--negative)"
                : msg ? (msg.tone === "err" ? "var(--negative)" : "var(--positive)")
                : "var(--text-muted)",
            }}>
              {invalid.length
                ? (invalid.length === 1
                    ? "1 match needs percentages adding up to 100"
                    : `${invalid.length} matches need percentages adding up to 100`)
                : msg ? msg.text
                : dirty.length ? `${dirty.length} unsaved` : "All picks saved"}
            </span>
            <button onClick={save} disabled={!canSave} style={{
              marginLeft: "auto", padding: "9px 20px", borderRadius: "var(--radius-md)", border: "none",
              background: canSave ? "var(--accent)" : "var(--bg-hover)",
              color: canSave ? "var(--text-on-accent)" : "var(--text-muted)",
              fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 700,
              cursor: canSave ? "pointer" : "default",
            }}>
              {busy ? "Saving…" : dirty.length ? `Save ${dirty.length}` : "Saved"}
            </button>
          </div>
        </React.Fragment>
      )}
    </div>
  );
}

window.PlayScreen = PlayScreen;
