/* Predict page -- three numbered blocks: choose match, pick winner, your details. */

function NumberedHeader({ n, label }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 22 }}>
      <div className="clip-paral font-display" style={{
        width: 54, height: 44, background: 'var(--orange)', color: '#fff',
        display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18, flexShrink: 0,
      }}>
        {String(n).padStart(2, '0')}
      </div>
      <h2 className="font-display" style={{
        margin: 0, fontSize: 22, letterSpacing: '0.06em', textTransform: 'uppercase',
      }}>
        {label}
      </h2>
    </div>
  );
}

/* ---------- Match list ---------- */
function MatchCard({ match, selected, onSelect }) {
  const disabled = match.status !== 'OPEN';
  const ko = formatKickoff(match.kickoffAt);

  return (
    <button
      onClick={() => !disabled && onSelect(match.id)}
      disabled={disabled}
      style={{
        background: 'none', border: 'none', padding: 0, width: '100%', textAlign: 'left',
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.55 : 1,
      }}>
      <div className="lift" style={{
        background: selected ? 'var(--orange-50)' : '#fff',
        border: selected ? '2px solid var(--orange)' : '1px solid var(--grey-200)',
        borderRadius: 10,
        overflow: 'hidden',
        boxShadow: selected
          ? '0 4px 20px rgba(241,86,35,0.22)'
          : (disabled ? 'none' : '0 2px 10px rgba(17,6,24,0.06)'),
        padding: 0,
        position: 'relative',
        ...(disabled ? {} : { cursor: 'pointer' }),
      }}
      onMouseEnter={(e) => {
        if (disabled || selected) return;
        e.currentTarget.style.borderColor = 'var(--orange)';
        e.currentTarget.style.transform = 'translateY(-2px)';
        e.currentTarget.style.boxShadow = '0 6px 18px rgba(241,86,35,0.16)';
      }}
      onMouseLeave={(e) => {
        if (disabled || selected) return;
        e.currentTarget.style.borderColor = 'var(--grey-200)';
        e.currentTarget.style.transform = 'translateY(0)';
        e.currentTarget.style.boxShadow = '0 2px 10px rgba(17,6,24,0.06)';
      }}>
        {/* Header: status + countdown */}
        <div style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          padding: '12px 18px', borderBottom: '1px solid var(--grey-200)',
          background: selected ? 'transparent' : 'var(--grey-50)',
        }}>
          <StatusBadge status={match.status} size="sm" />
          {match.status === 'OPEN' && <Countdown to={match.kickoffMs} tone="orange" size="sm" />}
          {match.status === 'CLOSED' && (
            <span className="font-mono" style={{ fontSize: 11, color: 'var(--grey-500)', letterSpacing: '0.08em' }}>AWAITING RESULT</span>
          )}
          {match.status === 'SETTLED' && match.settled && (
            <span className="font-mono" style={{ fontSize: 12, color: 'var(--ink)', letterSpacing: '0.04em', fontWeight: 700 }}>
              {match.settled.scoreA}-{match.settled.scoreB}
            </span>
          )}
        </div>

        {/* Top: teams + VS */}
        <div className="match-row" style={{
          display: 'grid',
          gridTemplateColumns: '1fr auto 1fr',
          alignItems: 'center', gap: 12,
          padding: '18px 18px 14px',
        }}>
          <TeamRow team={match.teamA} align="left" />
          <VSHex size={40} />
          <TeamRow team={match.teamB} align="right" />
        </div>

        {/* Foot strip */}
        <div style={{
          background: 'var(--grey-50)', borderTop: '1px solid var(--grey-200)',
          padding: '10px 18px',
          display: 'flex', gap: 20, fontSize: 12, color: 'var(--grey-600)', flexWrap: 'wrap',
        }}>
          <FootItem icon="calendar">{ko}</FootItem>
          <FootItem icon="flag">{match.stage}</FootItem>
          <FootItem icon="pin">{match.venue}</FootItem>
        </div>
      </div>
    </button>
  );
}

function TeamRow({ team, align }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      flexDirection: align === 'right' ? 'row-reverse' : 'row',
      textAlign: align,
      minWidth: 0,
    }}>
      <Flag code={team.flag} w={42} h={28} alt={team.name + ' flag'} />
      <div className="font-display" style={{ fontSize: 'clamp(15px, 3.4vw, 18px)', letterSpacing: '-0.01em', minWidth: 0 }}>{team.name}</div>
    </div>
  );
}

function FootItem({ icon, children }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>
      <Icon name={icon} size={14} color="var(--grey-500)" />
      {children}
    </span>
  );
}

/* ---------- Pick winner ---------- */
function PickButton({ kind, label, selected, onClick, disabled, flagCode }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className="lift"
      style={{
        position: 'relative',
        background: selected ? 'var(--orange-50)' : '#fff',
        border: selected ? '2px solid var(--orange)' : '1px solid var(--grey-200)',
        borderRadius: 10,
        boxShadow: selected ? '0 4px 16px rgba(241,86,35,0.22)' : '0 2px 8px rgba(17,6,24,0.05)',
        padding: '22px 18px',
        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14,
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.4 : 1,
        minHeight: 168,
      }}
      onMouseEnter={(e) => {
        if (disabled || selected) return;
        e.currentTarget.style.borderColor = 'var(--orange)';
      }}
      onMouseLeave={(e) => {
        if (disabled || selected) return;
        e.currentTarget.style.borderColor = 'var(--grey-200)';
      }}
    >
      {selected && (
        <span style={{
          position: 'absolute', top: 10, right: 10, width: 10, height: 10,
          background: 'var(--orange)', borderRadius: '50%',
        }} />
      )}
      {kind === 'draw' ? (
        <div style={{
          width: 78, height: 56, background: 'var(--ink)', display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icon name="scale" size={30} color="var(--orange)" />
        </div>
      ) : (
        <Flag code={flagCode} w={80} h={54} rounded={6} />
      )}
      <div className="font-display" style={{ fontSize: 15, textAlign: 'center', letterSpacing: '-0.01em' }}>
        {label}
      </div>
      <div style={{
        fontSize: 10, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--grey-500)', fontWeight: 700,
      }}>
        {kind === 'draw' ? 'Match drawn' : `${kind === 'A' ? 'Team A' : 'Team B'} wins`}
      </div>
    </button>
  );
}

/* ---------- Form field ---------- */
function Field({ label, hint, children, mono }) {
  return (
    <label style={{ display: 'block' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <span className="font-subheading" style={{ fontSize: 11.5, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink)' }}>{label}</span>
        {hint && <span style={{ fontSize: 11, color: 'var(--grey-500)', fontFamily: mono ? "'JetBrains Mono', monospace" : undefined }}>{hint}</span>}
      </div>
      <div style={{ marginTop: 8 }}>{children}</div>
    </label>
  );
}
const inputStyle = {
  width: '100%',
  border: '1.5px solid var(--grey-200)',
  borderRadius: 6,
  background: '#fff',
  padding: '13px 14px',
  fontSize: 15,
  color: 'var(--ink)',
  transition: 'border-color 0.15s, box-shadow 0.15s',
};

/* Inline pick button used inside match cards */
/* Compact single-line matchup (used when a match isn't pickable) */
function CompactMatchup({ m }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 9, padding: '12px 16px 4px', flexWrap: 'wrap' }}>
      <Flag code={m.teamA.flag} w={28} h={19} rounded={3} />
      <span className="font-display" style={{ fontSize: 13.5 }}>{m.teamA.name}</span>
      <span style={{ fontSize: 10, fontWeight: 800, color: 'var(--grey-300)', letterSpacing: '0.12em' }}>VS</span>
      <Flag code={m.teamB.flag} w={28} h={19} rounded={3} />
      <span className="font-display" style={{ fontSize: 13.5 }}>{m.teamB.name}</span>
    </div>
  );
}

function InlinePickBtn({ label, flag, isDraw, selected, onClick, disabled, side }) {
  // side: 'left' team -> name (outer) then flag (inner); 'right' team -> flag (inner) then name (outer)
  var flagEl = flag ? (
    <span className="pick-flag-frame"><Flag code={flag} w={34} h={23} rounded={0} /></span>
  ) : null;
  var nameEl = (
    <span className="font-display" style={{ fontSize: 13, fontWeight: 800, letterSpacing: '-0.01em', lineHeight: 1.1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
      {label}
    </span>
  );
  var check = selected && !isDraw ? (
    <span aria-hidden="true" style={{
      flexShrink: 0, width: 18, height: 18, borderRadius: '50%', background: 'rgba(255,255,255,0.25)',
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <Icon name="check" size={11} color="#fff" stroke={3} />
    </span>
  ) : null;
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      aria-pressed={selected}
      className={'pick-btn' + (selected ? ' pick-sel' : '')}
      style={{
        padding: '11px 13px',
        border: selected ? '2px solid var(--orange)' : '1px solid var(--grey-200)',
        borderRadius: 9,
        background: selected ? 'var(--orange)' : '#fff',
        color: selected ? '#fff' : disabled ? 'var(--grey-400)' : 'var(--ink)',
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.5 : 1,
        width: '100%',
        fontFamily: "'DM Sans', sans-serif",
        display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 9,
        justifyContent: isDraw ? 'center' : 'space-between',
        minHeight: 54,
      }}
    >
      {isDraw ? (
        <span className="font-display" style={{ fontSize: 14, fontWeight: 800, letterSpacing: '0.08em', lineHeight: 1, display: 'inline-flex', alignItems: 'center', gap: 7 }}>
          <span className="clip-hex" style={{
            width: 22, height: 22, background: selected ? 'rgba(255,255,255,0.22)' : 'var(--ink)',
            color: selected ? '#fff' : 'var(--orange)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 12, fontWeight: 900,
          }}>=</span>
          DRAW
        </span>
      ) : side === 'left' ? (
        <>{nameEl}<span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>{check}{flagEl}</span></>
      ) : (
        <><span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>{flagEl}{check}</span>{nameEl}</>
      )}
    </button>
  );
}

/* Crowd lean bar -- community pick split below pick buttons */
function CrowdLeanBar({ matchId, nameA, nameB }) {
  const dm = window.DASHBOARD && window.DASHBOARD.byMatch
    ? window.DASHBOARD.byMatch.find(function(x) { return x.id === matchId; })
    : null;
  const seed = matchId % 1000;
  const aRaw = dm ? dm.a_n : (30 + seed % 41);
  const dRaw = dm ? dm.draw_n : (12 + seed % 22);
  const bRaw = dm ? dm.b_n : (25 + (seed * 7) % 38);
  const total = aRaw + dRaw + bRaw;
  const pA = Math.round((aRaw / total) * 100);
  const pD = Math.round((dRaw / total) * 100);
  const pB = 100 - pA - pD;
  const leading = pA > pB ? 'A' : pB > pA ? 'B' : 'DRAW';
  return (
    <div style={{ padding: '2px 16px 14px' }}>
      <div style={{ fontSize: 9.5, color: 'var(--grey-400)', letterSpacing: '0.12em', textTransform: 'uppercase', marginBottom: 5, fontWeight: 700 }}>
        Crowd lean
      </div>
      <div style={{ display: 'flex', height: 5, borderRadius: 3, overflow: 'hidden', background: 'var(--grey-100)' }}>
        <div className="lean-segment" style={{ width: pA + '%', background: leading === 'A' ? 'var(--orange)' : 'var(--grey-300)', borderRadius: '3px 0 0 3px' }} />
        <div className="lean-segment" style={{ width: pD + '%', background: 'var(--grey-300)', animationDelay: '0.08s' }} />
        <div className="lean-segment" style={{ width: pB + '%', background: leading === 'B' ? 'var(--ink)' : 'var(--grey-300)', opacity: 0.7, borderRadius: '0 3px 3px 0', animationDelay: '0.12s' }} />
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 5, fontSize: 9.5, color: 'var(--grey-500)', fontWeight: 600 }}>
        <span style={{ color: leading === 'A' ? 'var(--orange)' : undefined }}>{nameA} {pA}%</span>
        <span>{pD}% Draw</span>
        <span style={{ color: leading === 'B' ? 'var(--ink)' : undefined }}>{pB}% {nameB}</span>
      </div>
    </div>
  );
}

/* ---------- Toast ---------- */
function SuccessToast({ message, onDone }) {
  useEffect(() => {
    const t = setTimeout(onDone, 4200);
    return () => clearTimeout(t);
  }, [onDone]);
  return (
    <div className="toast-in" style={{
      position: 'fixed', top: 22, left: '50%', zIndex: 50,
      background: 'var(--success-bg)', color: '#0f5f2e',
      border: '1px solid var(--success)',
      borderRadius: 8,
      boxShadow: '0 4px 16px rgba(15,95,46,0.12)',
      padding: '14px 22px',
      display: 'flex', alignItems: 'center', gap: 12, minWidth: 'min(320px, calc(100vw - 32px))', maxWidth: 'min(560px, calc(100vw - 32px))',
    }}>
      <div style={{ width: 28, height: 28, background: 'var(--success)', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <Icon name="check" size={18} color="#fff" stroke={3} />
      </div>
      <div style={{ fontSize: 14, fontWeight: 600, lineHeight: 1.4 }}>{message}</div>
    </div>
  );
}

/* ---------- Predict page -- flow: Details → Match+Pick (inline) → Submit ---------- */
function PredictPage() {
  // Step 1 -- Details (pre-filled from localStorage)
  const LS = { name: 'sf_name', phone: 'sf_phone', bill: 'sf_bill', amount: 'sf_amount' };
  const lsGet = (k) => { try { return localStorage.getItem(k) || ''; } catch { return ''; } };
  const lsSet = (k, v) => { try { localStorage.setItem(k, v); } catch {} };

  const [name,   setName]   = useState(() => lsGet(LS.name));
  const [phone,  setPhone]  = useState(() => lsGet(LS.phone)); // 7 digits only, +960 prefix fixed
  const [bill,   setBill]   = useState(() => lsGet(LS.bill));
  const [amount, setAmount] = useState(() => lsGet(LS.amount));
  // Match + Pick -- combined (one active at a time)
  const [selectedMatchId, setSelectedMatchId] = useState(null);
  const [pick, setPick] = useState(null); // 'A' | 'DRAW' | 'B'

  // Per-card submission state
  const [submitting,    setSubmitting]    = useState(null);  // matchId being submitted
  const [cardErrors,    setCardErrors]    = useState({});    // { matchId: errorMsg }
  const [submitted,     setSubmitted]     = useState(new Set()); // matchIds done
  const [submittedPicks, setSubmittedPicks] = useState({});  // { matchId: 'A'|'DRAW'|'B' }
  const [submittedTimes, setSubmittedTimes] = useState({});  // { matchId: { pickedAt, changedAt } }

  // Change-pick edit mode
  const [editingMatchId, setEditingMatchId] = useState(null); // matchId in edit mode
  const [editPick,       setEditPick]       = useState(null); // new pick being chosen
  const [updating,       setUpdating]       = useState(null); // matchId being updated

  const matchRef = useRef(null);
  const [showAllMatches, setShowAllMatches] = useState(false);
  const [teamFilter, setTeamFilter] = useState('');

  // Bill validation state -- explicit Validate button flow
  const [billValidation, setBillValidation] = useState({
    state: 'idle',   // 'idle' | 'validating' | 'valid' | 'invalid'
    error: null,
    errorCode: null, // 'NOT_FOUND' | 'AMOUNT_MISMATCH' | 'NOT_ELIGIBLE' | null
    maxVotes: 0, usedVotes: 0, remainingVotes: 0, amountMvr: 0,
    isNewBinding: false, // true = phone was just bound to bill for first time
  });

  // Persist to localStorage whenever fields change
  useEffect(() => { lsSet(LS.name,   name);   }, [name]);
  useEffect(() => { lsSet(LS.phone,  phone);  }, [phone]);
  useEffect(() => { lsSet(LS.bill,   bill);   }, [bill]);
  useEffect(() => { lsSet(LS.amount, amount); }, [amount]);

  // Reset validation when bill, amount, or phone changes
  useEffect(() => {
    setBillValidation(prev =>
      prev.state === 'idle' ? prev : { state: 'idle', error: null, errorCode: null, maxVotes: 0, usedVotes: 0, remainingVotes: 0, amountMvr: 0, isNewBinding: false }
    );
    setSubmitted(new Set());
    setSubmittedPicks({});
  }, [bill, amount, phone]);

  async function validateBill() {
    if (billValidation.state === 'validating') return;
    setBillValidation({ state: 'validating', error: null, errorCode: null, maxVotes: 0, usedVotes: 0, remainingVotes: 0, amountMvr: 0 });
    try {
      const r = await fetch('/api/validate-bill', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          bill_number: bill.trim().toUpperCase(),
          amount_myr:  parseFloat(amountClean),
          phone:       '+960' + phone.trim(),
          name:        name.trim(),
        }),
      });
      const data = await r.json();
      if (data.valid) {
        setBillValidation({ state: 'valid', error: null, errorCode: null, maxVotes: data.maxVotes, usedVotes: data.usedVotes, remainingVotes: data.remainingVotes, amountMvr: data.amountMvr, isNewBinding: !!data.isNewBinding });
        // Pre-populate prior predictions from DB
        const ids   = new Set((data.predictions || []).map(p => p.matchId));
        const pkMap = {};
        const tmMap = {};
        (data.predictions || []).forEach(p => {
          pkMap[p.matchId] = p.pick;
          tmMap[p.matchId] = { pickedAt: p.submittedAt || null, changedAt: p.changedAt || null };
        });
        setSubmitted(ids);
        setSubmittedPicks(pkMap);
        setSubmittedTimes(tmMap);
      } else {
        setBillValidation({ state: 'invalid', error: data.error || 'Receipt check failed.', errorCode: data.errorCode || null, maxVotes: 0, usedVotes: 0, remainingVotes: 0, amountMvr: 0 });
        setSubmitted(new Set());
        setSubmittedPicks({});
        setSubmittedTimes({});
      }
    } catch (_) {
      setBillValidation({ state: 'invalid', error: 'Network error. Check your connection and try again.', errorCode: null, maxVotes: 0, usedVotes: 0, remainingVotes: 0, amountMvr: 0 });
    }
  }

  // All upcoming matches -- open for voting until kickoff
  const allUpcoming = useMemo(() => {
    const nowMs = Date.now();
    return [...MATCHES]
      .filter(m => m.status !== 'SETTLED' && m.status !== 'CLOSED' && nowMs < m.kickoffMs)
      .sort((a, b) => a.kickoffMs - b.kickoffMs);
  }, []);

  // Full display list: upcoming + any already-predicted matches not in upcoming
  // (covers closed/settled/past-kickoff matches the user predicted in a prior session)
  const sortedMatches = useMemo(() => {
    const upcomingIds = new Set(allUpcoming.map(m => m.id));
    // Add predicted matches that are no longer in upcoming list
    const predicted = submitted.size > 0
      ? MATCHES.filter(m => submitted.has(m.id) && !upcomingIds.has(m.id))
      : [];
    const combined = [...predicted, ...allUpcoming];
    // Predicted first (stable order), then upcoming by kickoff
    if (submitted.size === 0) return combined;
    return combined.sort((a, b) => {
      const aDone = submitted.has(a.id) ? 0 : 1;
      const bDone = submitted.has(b.id) ? 0 : 1;
      if (aDone !== bDone) return aDone - bDone;
      return a.kickoffMs - b.kickoffMs;
    });
  }, [allUpcoming, submitted]);

  // Team options for the filter (all teams in the predictable list)
  const teamOptions = useMemo(() => {
    const s = {};
    sortedMatches.forEach(m => {
      if (m.teamA && m.teamA.name) s[m.teamA.name] = true;
      if (m.teamB && m.teamB.name) s[m.teamB.name] = true;
    });
    return Object.keys(s).sort();
  }, [sortedMatches]);

  // Apply team filter; when filtering, show all that team's matches (no 6-limit)
  const teamFilteredMatches = teamFilter
    ? sortedMatches.filter(m => (m.teamA && m.teamA.name === teamFilter) || (m.teamB && m.teamB.name === teamFilter))
    : sortedMatches;
  const visibleMatches = teamFilter || showAllMatches ? teamFilteredMatches : teamFilteredMatches.slice(0, 6);

  // A match is voteable if kickoff hasn't happened yet
  const isVoteable = (m) => Date.now() < m.kickoffMs;

  // ── Validation ──
  const nameOk    = name.trim().length >= 2 && name.trim().length <= 80;
  const phoneOk   = /^\d{7}$/.test(phone.trim());
  const billOk    = bill.trim().length > 0 && bill.length <= 50;
  // Strip currency symbols/commas before parsing amount
  const amountClean = String(amount).replace(/[^0-9.]/g, '');
  const amountOk  = !isNaN(parseFloat(amountClean)) && parseFloat(amountClean) > 0;
  // detailsOk: bill validated against DB. Name checked separately at canSubmit.
  const detailsOk = phoneOk && billValidation.state === 'valid';

  // Vote quota comes from validated bill
  const maxVotes       = billValidation.maxVotes;
  const remainingVotes = Math.max(0, maxVotes - submitted.size);

  // Enable Validate button -- bill + amount + phone all required
  // Phone is bound to bill on first use, so must be provided upfront
  const canValidate = billOk && amountOk && phoneOk && billValidation.state !== 'validating';
  // Name missing message shown inline on match cards
  const nameMissing = billValidation.state === 'valid' && !nameOk;

  // Auto-scroll to matches when details become valid
  const prevDetailsOk = useRef(false);
  useEffect(() => {
    if (detailsOk && !prevDetailsOk.current && matchRef.current) {
      setTimeout(() => {
        const y = matchRef.current.getBoundingClientRect().top + window.scrollY - 80;
        window.scrollTo({ top: y, behavior: 'smooth' });
      }, 120);
    }
    prevDetailsOk.current = detailsOk;
  }, [detailsOk]);

  function selectPick(matchId, pickValue) {
    if (selectedMatchId === matchId && pick === pickValue) {
      setPick(null); // deselect
    } else {
      setSelectedMatchId(matchId);
      setPick(pickValue);
      // Clear error for this card when user changes pick
      setCardErrors(prev => { const n = {...prev}; delete n[matchId]; return n; });
    }
  }

  async function updatePick(m, newPick) {
    if (updating || !newPick || newPick === submittedPicks[m.id]) {
      setEditingMatchId(null);
      setEditPick(null);
      return;
    }
    setUpdating(m.id);
    setCardErrors(prev => { const n = {...prev}; delete n[m.id]; return n; });
    try {
      const r = await fetch('/api/update-prediction', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          phone:     '+960' + phone.trim(),
          bill:      bill.trim().toUpperCase(),
          matchId:   m.id,
          pick:      newPick,
          kickoffMs: m.kickoffMs,
        }),
      });
      const data = await r.json();
      if (!r.ok) {
        setCardErrors(prev => ({ ...prev, [m.id]: data.error || 'Update failed.' }));
      } else {
        setSubmittedPicks(prev => ({ ...prev, [m.id]: newPick }));
        const changedAt = data.changedAt || new Date().toISOString();
        setSubmittedTimes(prev => ({ ...prev, [m.id]: { ...(prev[m.id] || {}), changedAt } }));
      }
    } catch (_) {
      setCardErrors(prev => ({ ...prev, [m.id]: 'Network error. Try again.' }));
    }
    setUpdating(null);
    setEditingMatchId(null);
    setEditPick(null);
  }

  async function submitCard(m, cardPick) {
    if (submitting || submitted.has(m.id)) return;
    setCardErrors(prev => { const n = {...prev}; delete n[m.id]; return n; });
    setSubmitting(m.id);
    try {
      const res = await fetch('/api/submit-prediction', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name:       name.trim(),
          phone:      '+960' + phone.trim(),
          bill:       bill.trim().toUpperCase(),
          amount:     billValidation.amountMvr,  // always use DB-verified amount
          matchId:    m.id,
          matchLabel: `${m.teamA.name} vs ${m.teamB.name}`,
          pick:       cardPick,
        }),
      });
      const data = await res.json();
      if (!res.ok) {
        setCardErrors(prev => ({ ...prev, [m.id]: data.error || 'Something went wrong. Try again.' }));
        setSubmitting(null);
        return;
      }
      // Success -- mark card as done, record the pick + timestamp (prefer server timestamp)
      const pickedAt = data.submittedAt || new Date().toISOString();
      setSubmitted(prev => new Set([...prev, m.id]));
      setSubmittedPicks(prev => ({ ...prev, [m.id]: cardPick }));
      setSubmittedTimes(prev => ({ ...prev, [m.id]: { pickedAt, changedAt: null } }));
      setSelectedMatchId(null);
      setPick(null);
      // Update local remainingVotes (submitted.size drives it reactively)
    } catch (e) {
      setCardErrors(prev => ({ ...prev, [m.id]: 'Network error. Check your connection.' }));
    }
    setSubmitting(null);
  }

  const upcomingCount = allUpcoming.length;
  // For mobile sticky: find the active pick context
  const activeMatch = visibleMatches.find(m => m.id === selectedMatchId);

  return (
    <main style={{ paddingBottom: 120 }}>

      <InnerPageBanner />

      <PageBand
        eyebrow="Make your call"
        title={<>Pick the winner. <span style={{ color: 'var(--orange)' }}>Lock in your shot.</span></>}
        sub="Enter your receipt number, choose the winning team, and submit. One receipt, one pick per match."
        right={
          <div className="hide-mobile" style={{ display: 'flex', gap: 24, alignItems: 'center' }}>
            <div style={{ textAlign: 'right' }}>
              <div className="font-highlight" style={{ fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--grey-500)' }}>Upcoming matches</div>
              <div className="font-display" style={{ fontSize: 36, lineHeight: 1, marginTop: 4 }}>{upcomingCount}</div>
            </div>
          </div>
        }
      />

      <section style={{ padding: 'clamp(20px, 4vw, 32px) 0 clamp(40px, 8vw, 60px)' }}>
        <div style={{ maxWidth: 860, margin: '0 auto', padding: '0 clamp(16px, 4vw, 22px)' }}>

          {/* ── Block 1 -- Your details ── */}
          <NumberedHeader n={1} label="Your details" />
          <div className="form-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16 }}>

            <Field label="Full name" hint={`${name.length}/80`}>
              <input
                type="text" value={name} maxLength={80} autoFocus
                onChange={(e) => setName(e.target.value)}
                placeholder="Full name"
                style={inputStyle}
              />
            </Field>

            <Field label="Mobile number" hint="Maldives number, 7 digits">
              <div style={{ display: 'flex', alignItems: 'stretch', border: '1.5px solid var(--grey-200)', borderRadius: 6, overflow: 'hidden', background: '#fff', transition: 'border-color 0.15s, box-shadow 0.15s' }}>
                <div className="font-mono" style={{
                  padding: '13px 12px 13px 14px', background: 'var(--grey-50)',
                  borderRight: '1.5px solid var(--grey-200)',
                  fontSize: 15, fontWeight: 700, color: 'var(--ink)',
                  flexShrink: 0, display: 'flex', alignItems: 'center', userSelect: 'none',
                }}>
                  +960
                </div>
                <input
                  type="tel" inputMode="numeric"
                  value={phone}
                  maxLength={7}
                  onChange={(e) => setPhone(e.target.value.replace(/\D/g, '').slice(0, 7))}
                  placeholder="7-digit number"
                  className="font-mono"
                  style={{ ...inputStyle, border: 'none', flex: 1, borderRadius: 0, boxShadow: 'none' }}
                />
              </div>
            </Field>

            <Field label="Bill / receipt number" hint="e.g. SL-123456/26">
              <input
                type="text" value={bill} maxLength={50}
                onChange={(e) => setBill(e.target.value.toUpperCase())}
                placeholder="Bill number"
                autoCapitalize="characters" autoCorrect="off" spellCheck="false"
                className="input-upper font-mono"
                aria-invalid={billValidation.state === 'invalid'}
                aria-describedby="bill-error"
                style={{
                  ...inputStyle,
                  fontFamily: "'JetBrains Mono', monospace",
                  borderColor: billValidation.state === 'valid' ? 'var(--success)' : billValidation.state === 'invalid' ? '#ef4444' : undefined,
                }}
              />
            </Field>

            <Field label="Bill amount (MVR)" hint="Min. MVR 1,000 to qualify">
              <input
                type="number" inputMode="decimal" value={amount} min="0.01" step="0.01"
                onChange={(e) => setAmount(e.target.value)}
                placeholder="e.g. 1500"
                aria-invalid={billValidation.state === 'invalid'}
                aria-describedby="bill-error"
                style={{
                  ...inputStyle,
                  borderColor: billValidation.state === 'valid' ? 'var(--success)' : billValidation.state === 'invalid' ? '#ef4444' : undefined,
                }}
              />
            </Field>
          </div>

          {/* ── Validate bill button + result ── */}
          <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
            <button
              onClick={validateBill}
              disabled={!canValidate}
              className="font-display btn-press"
              style={{
                background: canValidate ? 'var(--ink)' : 'var(--grey-300)',
                color: '#fff', border: 'none', borderRadius: 6,
                padding: '12px 22px', fontSize: 13, letterSpacing: '0.06em', textTransform: 'uppercase',
                boxShadow: canValidate ? '0 4px 12px rgba(17,6,24,0.20)' : 'none',
                cursor: canValidate ? 'pointer' : 'not-allowed',
                display: 'inline-flex', alignItems: 'center', gap: 8,
                transition: 'background 0.15s',
              }}
            >
              {billValidation.state === 'validating'
                ? <><span style={{ width: 14, height: 14, border: '2px solid rgba(255,255,255,0.4)', borderTopColor: '#fff', borderRadius: '50%', display: 'inline-block', animation: 'spin 0.7s linear infinite' }} /> Validating...</>
                : <><Icon name="check" size={14} stroke={2.5} /> Check receipt</>
              }
            </button>
            {!canValidate && billValidation.state !== 'validating' && (
              <span style={{ fontSize: 12, color: 'var(--grey-400)', fontWeight: 500 }}>
                {!phoneOk ? 'Enter your mobile number first' : !billOk ? 'Enter your bill number' : !amountOk ? 'Enter the bill amount' : 'Fill all fields to check'}
              </span>
            )}

            {/* Validation result */}
            {billValidation.state === 'valid' && (
              <div style={{
                display: 'inline-flex', alignItems: 'flex-start', gap: 10,
                background: 'var(--success-bg)', border: '1px solid var(--success)',
                borderRadius: 8, padding: '10px 16px',
              }}>
                <div style={{ width: 24, height: 24, borderRadius: '50%', background: 'var(--success)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, marginTop: 1 }}>
                  <Icon name="check" size={14} color="#fff" stroke={3} />
                </div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: '#0f5f2e' }}>
                    Receipt confirmed ·{' '}
                    <span style={{ color: 'var(--success)', fontSize: 16 }}>{remainingVotes}</span>
                    {' '}pick{remainingVotes !== 1 ? 's' : ''} remaining
                  </div>
                  {submitted.size > 0 && (
                    <div style={{ fontSize: 11.5, color: '#166534', marginTop: 2 }}>
                      {submitted.size} used · {maxVotes} total
                    </div>
                  )}
                  {billValidation.isNewBinding && (
                    <div style={{ fontSize: 11.5, color: '#166534', marginTop: 4, display: 'flex', alignItems: 'center', gap: 5 }}>
                      <Icon name="pin" size={12} color="#166534" />
                      Mobile +960 {phone} linked to this bill.
                    </div>
                  )}
                  {!billValidation.isNewBinding && (
                    <div style={{ fontSize: 11.5, color: '#166534', marginTop: 4 }}>
                      Phone confirmed.
                    </div>
                  )}
                </div>
              </div>
            )}

            {/* Invalid bill error */}
            {billValidation.state === 'invalid' && (
              <div id="bill-error" role="alert" style={{
                display: 'inline-flex', alignItems: 'flex-start', gap: 10,
                background: '#fee2e2', border: '1px solid #fca5a5',
                borderRadius: 8, padding: '12px 16px',
                maxWidth: 480,
              }}>
                <Icon name="bolt" size={14} color="#b91c1c" style={{ flexShrink: 0, marginTop: 2 }} />
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, color: '#b91c1c', lineHeight: 1.4 }}>
                    {billValidation.errorCode === 'NOT_FOUND'
                      ? 'Receipt not found'
                      : billValidation.errorCode === 'AMOUNT_MISMATCH'
                      ? 'Amount does not match'
                      : billValidation.errorCode === 'NOT_ELIGIBLE'
                      ? 'Bill not eligible for campaign'
                      : 'Could not verify receipt'}
                  </div>
                  <div style={{ fontSize: 12, color: '#991b1b', marginTop: 4, lineHeight: 1.5, fontWeight: 400 }}>
                    {billValidation.errorCode === 'NOT_FOUND' ? (
                      <>
                        Check your bill number is typed correctly. If it is, your purchase may not qualify: only SunFront receipts of{' '}
                        <strong>MVR 1,000 or above</strong> are eligible for this campaign.
                      </>
                    ) : billValidation.errorCode === 'NOT_ELIGIBLE' ? (
                      <>
                        Your receipt was found but the total is below the minimum. Only SunFront purchases of{' '}
                        <strong>MVR 1,000 or above</strong> qualify for World Cup Foari 2026.
                      </>
                    ) : (
                      billValidation.error
                    )}
                  </div>
                </div>
              </div>
            )}
          </div>

          {/* ── Block 2 -- Choose match + pick inline ── */}
          <div ref={matchRef} style={{
            marginTop: 48,
            opacity: detailsOk ? 1 : 0.35,
            pointerEvents: detailsOk ? 'auto' : 'none',
            transition: 'opacity 0.25s',
            cursor: detailsOk ? 'auto' : 'not-allowed',
          }}>
            <NumberedHeader n={2} label="Pick your winner" />
            {!detailsOk && (
              <div style={{
                border: '2px dashed var(--grey-300)', padding: '22px 24px', borderRadius: 8,
                color: 'var(--grey-400)', fontSize: 14, marginBottom: 14, textAlign: 'center',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
              }}>
                <Icon name="bolt" size={14} color="var(--grey-300)" />
                Validate your receipt above to unlock match picks.
              </div>
            )}
            {detailsOk && nameMissing && (
              <div style={{
                display: 'flex', alignItems: 'center', gap: 10,
                background: '#fffbeb', border: '1px solid #fbbf24',
                borderRadius: 8, padding: '10px 16px', marginBottom: 14,
                fontSize: 13, fontWeight: 600, color: '#92400e',
              }}>
                <Icon name="bolt" size={14} color="#d97706" />
                Enter your full name above to submit predictions.
              </div>
            )}

            {/* Team filter -- find your team's matches fast */}
            {detailsOk && teamOptions.length > 0 && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16, flexWrap: 'wrap' }}>
                <span style={{ fontSize: 12, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--grey-500)' }}>Find your team</span>
                <select
                  value={teamFilter}
                  onChange={(e) => setTeamFilter(e.target.value)}
                  aria-label="Filter matches by team"
                  style={{
                    appearance: 'none', WebkitAppearance: 'none',
                    background: teamFilter ? 'var(--ink)' : '#fff',
                    color: teamFilter ? '#fff' : 'var(--grey-700)',
                    border: teamFilter ? '2px solid var(--ink)' : '2px solid var(--grey-200)',
                    borderRadius: 6, padding: '8px 30px 8px 14px',
                    fontWeight: 700, fontSize: 13, letterSpacing: '0.04em', cursor: 'pointer', maxWidth: 220,
                    backgroundImage: "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%23999' stroke-width='1.6' fill='none' stroke-linecap='round'/></svg>\")",
                    backgroundRepeat: 'no-repeat', backgroundPosition: 'right 12px center',
                  }}>
                  <option value="">All teams</option>
                  {teamOptions.map(t => <option key={t} value={t}>{t}</option>)}
                </select>
                {teamFilter && (
                  <button onClick={() => setTeamFilter('')} aria-label="Clear team filter"
                    style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--grey-500)', fontSize: 18, lineHeight: 1, padding: '2px 4px' }}>&times;</button>
                )}
              </div>
            )}

            {visibleMatches.length === 0 && (
              <div style={{
                textAlign: 'center', padding: '52px 24px',
                border: '1.5px dashed var(--grey-200)', borderRadius: 14,
                background: 'var(--grey-50)',
              }}>
                <div aria-hidden="true" style={{ fontSize: 52, marginBottom: 14, lineHeight: 1 }}>{'⚽'}</div>
                <div className="font-display" style={{ fontSize: 20, color: 'var(--ink)', marginBottom: 8, letterSpacing: '0.04em' }}>No Open Matches Yet</div>
                <div style={{ fontSize: 14, color: 'var(--grey-500)', lineHeight: 1.6, maxWidth: 360, margin: '0 auto' }}>
                  Predictions open shortly before each group stage match kicks off. Check back soon!
                </div>
              </div>
            )}

            <div style={{ display: 'grid', gap: 14 }}>
              {visibleMatches.map(m => {
                const isSubmitted  = submitted.has(m.id);
                const isSubmitting = submitting === m.id;
                const isSelected   = selectedMatchId === m.id;
                const currentPick  = isSelected ? pick : null;
                const voteable     = isVoteable(m);
                const cardError    = cardErrors[m.id];
                // Can submit: bill validated + name filled + match open + votes left
                const canSubmit    = isSelected && currentPick && detailsOk && nameOk && voteable && !isSubmitted && remainingVotes > 0;

                // ── Submitted success state -- full match visualization ──
                if (isSubmitted) {
                  const myPick    = submittedPicks[m.id];
                  const ko        = formatKickoff(m.kickoffAt);
                  const canChange = voteable && detailsOk; // match open + bill validated
                  const isEditing = editingMatchId === m.id;
                  const times     = submittedTimes[m.id] || {};
                  const fmtTs = (iso) => {
                    if (!iso) return null;
                    const d = new Date(iso);
                    return d.toLocaleString('en-MV', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: true });
                  };
                  const isUpdating = updating === m.id;

                  // Compute settled outcome for result reveal
                  let settledOutcome = null;
                  if (m.status === 'SETTLED' && m.settled) {
                    if (m.settled.draw) {
                      settledOutcome = myPick === 'DRAW' ? 'correct' : 'wrong';
                    } else {
                      const winnerIsA = m.teamAKey === m.settled.winner;
                      settledOutcome = (myPick === 'A' && winnerIsA) || (myPick === 'B' && !winnerIsA) ? 'correct' : 'wrong';
                    }
                  }
                  const revealClass = settledOutcome === 'correct' ? 'flash-correct' : settledOutcome === 'wrong' ? 'shake-wrong' : '';

                  // Ticket header state
                  const ticketBg = settledOutcome === 'correct' ? 'var(--success)' : settledOutcome === 'wrong' ? '#ef4444' : isEditing ? 'var(--orange)' : 'var(--ink)';
                  const ticketIcon = settledOutcome === 'wrong' ? 'close' : isEditing ? 'bolt' : 'check';
                  const ticketMsg = settledOutcome === 'correct' ? 'Correct prediction!' : settledOutcome === 'wrong' ? 'Wrong this time' : isEditing ? 'Change your pick' : 'Prediction locked in!';

                  return (
                    <div key={m.id} className={revealClass} style={{
                      border: '2px solid var(--ink)',
                      borderRadius: 12,
                      overflow: 'hidden',
                      background: '#fff',
                      boxShadow: settledOutcome === 'correct' ? '0 0 0 3px rgba(34,197,94,0.25)' : settledOutcome === 'wrong' ? '0 0 0 2px rgba(239,68,68,0.2)' : '6px 6px 0 0 var(--ink)',
                    }}>
                      {/* Ticket header */}
                      <div style={{
                        background: ticketBg, color: '#fff',
                        padding: '10px 16px',
                        display: 'flex', alignItems: 'center', gap: 10,
                        fontSize: 13, fontWeight: 700, letterSpacing: '0.04em',
                      }}>
                        <div style={{ width: 22, height: 22, borderRadius: '50%', background: 'rgba(255,255,255,0.22)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                          <Icon name={ticketIcon} size={13} color="#fff" stroke={3} />
                        </div>
                        {ticketMsg}
                        <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>
                          {voteable && !settledOutcome && (
                            <Countdown to={m.kickoffMs} tone="ink" size="sm" />
                          )}
                          {!voteable && !settledOutcome && (
                            <span style={{ fontSize: 11, fontWeight: 600, opacity: 0.85, letterSpacing: '0.08em' }}>MATCH STARTED</span>
                          )}
                          {settledOutcome && m.settled && (
                            <span className="font-mono" style={{ fontSize: 13, fontWeight: 800, letterSpacing: '0.06em' }}>
                              {m.settled.scoreA}:{m.settled.scoreB}
                            </span>
                          )}
                        </span>
                      </div>
                      {/* Ticket barcode strip */}
                      <div className="stripes-bg" style={{ height: 6 }} />

                      {/* Teams */}
                      <div style={{
                        display: 'grid', gridTemplateColumns: '1fr auto 1fr',
                        alignItems: 'center', gap: 10, padding: '16px 16px 10px',
                      }}>
                        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}>
                          <Flag code={m.teamA.flag} w={42} h={28} />
                          <div className="font-display" style={{ fontSize: 14, textAlign: 'center', lineHeight: 1.2 }}>{m.teamA.name}</div>
                        </div>
                        <VSHex size={30} />
                        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}>
                          <Flag code={m.teamB.flag} w={42} h={28} />
                          <div className="font-display" style={{ fontSize: 14, textAlign: 'center', lineHeight: 1.2 }}>{m.teamB.name}</div>
                        </div>
                      </div>

                      {/* Pick buttons -- read-only or editable */}
                      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, padding: '0 16px 14px' }}>
                        {[
                          { key: 'A',    label: m.teamA.name },
                          { key: 'DRAW', label: 'Draw' },
                          { key: 'B',    label: m.teamB.name },
                        ].map(opt => {
                          const isPrevPick  = myPick === opt.key;
                          const isNewPick   = isEditing && editPick === opt.key;
                          const active      = isEditing ? isNewPick : isPrevPick;
                          const dimmed      = isEditing && !isNewPick && !isPrevPick;
                          return isEditing ? (
                            <button key={opt.key}
                              onClick={() => setEditPick(prev => prev === opt.key ? null : opt.key)}
                              disabled={isUpdating}
                              style={{
                                padding: '9px 6px',
                                border: isNewPick ? '2px solid var(--orange)' : isPrevPick ? '2px dashed var(--grey-300)' : '1px solid var(--grey-200)',
                                borderRadius: 8,
                                background: isNewPick ? 'var(--orange)' : isPrevPick ? 'var(--grey-50)' : '#fff',
                                color: isNewPick ? '#fff' : isPrevPick ? 'var(--grey-400)' : 'var(--ink)',
                                fontSize: 12, fontWeight: 700, textAlign: 'center',
                                cursor: 'pointer', transition: 'all 0.12s',
                                display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
                              }}>
                              {isNewPick && <Icon name="check" size={12} color="#fff" stroke={3} />}
                              {opt.label}
                              {isPrevPick && !isNewPick && <span style={{ fontSize: 9, opacity: 0.6 }}>current</span>}
                            </button>
                          ) : (
                            <div key={opt.key} style={{
                              padding: '9px 6px',
                              border: active ? '2px solid var(--success)' : '1px solid var(--grey-200)',
                              borderRadius: 8,
                              background: active ? 'var(--success)' : 'var(--grey-50)',
                              color: active ? '#fff' : 'var(--grey-400)',
                              fontSize: 12, fontWeight: active ? 700 : 500,
                              textAlign: 'center',
                              display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
                            }}>
                              {active && <Icon name="check" size={12} color="#fff" stroke={3} />}
                              {opt.label}
                            </div>
                          );
                        })}
                      </div>

                      {/* Error */}
                      {cardError && (
                        <div style={{ margin: '0 16px 10px', padding: '8px 12px', background: '#fee2e2', border: '1px solid #fca5a5', borderRadius: 6, fontSize: 12, color: '#b91c1c', fontWeight: 600 }}>
                          {cardError}
                        </div>
                      )}

                      {/* Ticket perforation divider */}
                      <div style={{
                        borderTop: '2px dashed var(--grey-200)',
                        margin: '0 0',
                        position: 'relative',
                      }}>
                        <span style={{ position: 'absolute', left: -1, top: -10, width: 18, height: 18, background: 'var(--grey-50)', borderRadius: '50%', border: '2px solid var(--grey-200)' }} />
                        <span style={{ position: 'absolute', right: -1, top: -10, width: 18, height: 18, background: 'var(--grey-50)', borderRadius: '50%', border: '2px solid var(--grey-200)' }} />
                      </div>

                      {/* Stub footer */}
                      <div style={{
                        background: 'var(--grey-50)', padding: '8px 16px', fontSize: 11.5, color: 'var(--grey-500)',
                        display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center',
                      }}>
                        <FootItem icon="calendar">{ko}</FootItem>
                        <FootItem icon="pin">{m.venue}</FootItem>
                        {times.pickedAt && (
                          <FootItem icon="check">Picked {fmtTs(times.pickedAt)}</FootItem>
                        )}
                        {times.changedAt && (
                          <FootItem icon="bolt">Changed {fmtTs(times.changedAt)}</FootItem>
                        )}
                        {/* Change pick / confirm / cancel */}
                        {canChange && !isEditing && (
                          <button
                            aria-label={'Change your prediction for ' + m.teamA.name + ' vs ' + m.teamB.name}
                            onClick={() => { setEditingMatchId(m.id); setEditPick(null); setCardErrors(prev => { const n={...prev}; delete n[m.id]; return n; }); }}
                            style={{
                              marginLeft: 'auto', background: 'none', border: '1px solid var(--grey-300)',
                              borderRadius: 5, padding: '4px 12px', fontSize: 11, fontWeight: 700,
                              color: 'var(--grey-600)', cursor: 'pointer', letterSpacing: '0.04em',
                              textTransform: 'uppercase',
                            }}>
                            Change pick
                          </button>
                        )}
                        {isEditing && (
                          <div style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
                            <button
                              aria-label="Cancel editing prediction"
                              onClick={() => { setEditingMatchId(null); setEditPick(null); }}
                              disabled={isUpdating}
                              style={{
                                background: 'none', border: '1px solid var(--grey-300)',
                                borderRadius: 5, padding: '5px 12px', fontSize: 11, fontWeight: 700,
                                color: 'var(--grey-500)', cursor: 'pointer',
                              }}>
                              Cancel
                            </button>
                            <button
                              aria-label="Confirm new prediction"
                              onClick={() => updatePick(m, editPick)}
                              disabled={!editPick || editPick === myPick || isUpdating}
                              style={{
                                background: editPick && editPick !== myPick ? 'var(--orange)' : 'var(--grey-300)',
                                border: 'none', borderRadius: 5, padding: '5px 14px',
                                fontSize: 11, fontWeight: 800, color: '#fff', cursor: editPick && editPick !== myPick ? 'pointer' : 'not-allowed',
                                letterSpacing: '0.06em', textTransform: 'uppercase', display: 'flex', alignItems: 'center', gap: 6,
                              }}>
                              {isUpdating
                                ? <><span style={{ width: 10, height: 10, border: '2px solid rgba(255,255,255,0.4)', borderTopColor: '#fff', borderRadius: '50%', display: 'inline-block', animation: 'spin 0.7s linear infinite' }} /> Updating</>
                                : 'Confirm change'}
                            </button>
                          </div>
                        )}
                        {!canChange && !isEditing && (
                          <span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--grey-400)', fontWeight: 600, letterSpacing: '0.04em' }}>
                            Match locked
                          </span>
                        )}
                      </div>
                    </div>
                  );
                }

                return (
                  <div key={m.id} className="match-card" style={{
                    border: isSelected ? '2px solid var(--orange)' : '1px solid var(--grey-200)',
                    borderRadius: 12,
                    background: isSelected ? 'var(--orange-50)' : '#fff',
                    boxShadow: isSelected ? '0 8px 28px rgba(241,86,35,0.22)' : '0 2px 8px rgba(17,6,24,0.06)',
                    overflow: 'hidden',
                  }}>
                    {/* Sporty striped top accent (selected matches) */}
                    {isSelected && <div className="match-card-accent" aria-hidden="true" />}
                    {/* Card header -- countdown to kickoff = prediction close */}
                    <div style={{
                      display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                      padding: '10px 16px', borderBottom: '1px solid var(--grey-100)',
                      background: isSelected ? 'transparent' : 'var(--grey-50)',
                    }}>
                      <StatusBadge status={m.status} size="sm" />
                      {voteable && <Countdown to={m.kickoffMs} tone={m.status === 'OPEN' ? 'orange' : 'light'} size="sm" />}
                    </div>

                    {/* Pick buttons -- teams shown here (open) or a compact line (closed) */}
                    {!voteable ? (
                      <>
                        <CompactMatchup m={m} />
                        <div style={{ padding: '4px 16px 14px', fontSize: 11.5, color: 'var(--grey-400)', fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', textAlign: 'center' }}>
                          Predictions closed · Match started
                        </div>
                      </>
                    ) : remainingVotes === 0 && !isSelected && detailsOk ? (
                      <>
                        <CompactMatchup m={m} />
                        <div style={{ padding: '4px 16px 14px', fontSize: 12.5, color: 'var(--grey-500)', textAlign: 'center' }}>
                          No votes remaining -- increase your bill amount to vote on more matches.
                        </div>
                      </>
                    ) : (
                      <>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, padding: '12px 16px 10px' }}>
                          <InlinePickBtn side="left" label={m.teamA.name} flag={m.teamA.flag} selected={currentPick === 'A'} disabled={!voteable || (remainingVotes === 0 && !isSelected)} onClick={() => selectPick(m.id, 'A')} />
                          <InlinePickBtn isDraw selected={currentPick === 'DRAW'} disabled={!voteable || (remainingVotes === 0 && !isSelected)} onClick={() => selectPick(m.id, 'DRAW')} />
                          <InlinePickBtn side="right" label={m.teamB.name} flag={m.teamB.flag} selected={currentPick === 'B'} disabled={!voteable || (remainingVotes === 0 && !isSelected)} onClick={() => selectPick(m.id, 'B')} />
                        </div>
                        <CrowdLeanBar matchId={m.id} nameA={m.teamA.name} nameB={m.teamB.name} />
                      </>
                    )}

                    {/* ── Lock button + confirmation -- appears on this card when pick made ── */}
                    {canSubmit && (
                      <div style={{
                        margin: '0 16px 16px',
                        background: '#fff',
                        border: '1.5px solid var(--orange)',
                        borderRadius: 10,
                        padding: '14px 16px',
                        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                        gap: 12, flexWrap: 'wrap',
                      }}>
                        <div style={{ fontSize: 13.5, color: 'var(--ink)', fontWeight: 600, flex: 1, minWidth: 0 }}>
                          <span style={{ color: 'var(--grey-500)', fontWeight: 400, fontSize: 12 }}>Your pick: </span>
                          <strong style={{ color: 'var(--orange)' }}>
                            {currentPick === 'A' ? m.teamA.name : currentPick === 'B' ? m.teamB.name : 'Draw'}
                          </strong>
                        </div>
                        <button
                          aria-label={'Lock prediction: ' + (currentPick === 'A' ? m.teamA.name : currentPick === 'B' ? m.teamB.name : 'Draw') + ' wins ' + m.teamA.name + ' vs ' + m.teamB.name}
                          onClick={() => submitCard(m, currentPick)}
                          disabled={isSubmitting}
                          className="font-display btn-press"
                          style={{
                            background: 'var(--orange)', color: '#fff', border: 'none',
                            borderRadius: 6, flexShrink: 0,
                            padding: '12px 20px', fontSize: 13, letterSpacing: '0.06em', textTransform: 'uppercase',
                            boxShadow: '0 4px 14px rgba(241,86,35,0.30)',
                            display: 'inline-flex', alignItems: 'center', gap: 8,
                            cursor: isSubmitting ? 'wait' : 'pointer',
                          }}
                        >
                          <Icon name="check" size={15} stroke={3} />
                          {isSubmitting ? 'Saving...' : 'Lock My Prediction'}
                        </button>
                      </div>
                    )}

                    {/* Per-card error */}
                    {cardError && (
                      <div style={{
                        margin: '0 16px 14px', padding: '10px 14px',
                        background: '#fee2e2', color: '#b91c1c',
                        border: '1px solid #fecaca', borderRadius: 6,
                        fontSize: 13, fontWeight: 600,
                      }}>{cardError}</div>
                    )}

                    {/* Footer */}
                    <div style={{
                      background: isSelected ? 'rgba(241,86,35,0.04)' : 'var(--grey-50)',
                      borderTop: '1px solid var(--grey-100)',
                      padding: '8px 16px', fontSize: 11.5, color: 'var(--grey-500)',
                      display: 'flex', gap: 16,
                    }}>
                      <FootItem icon="flag">{m.stage}</FootItem>
                      <FootItem icon="pin">{m.venue}</FootItem>
                    </div>
                  </div>
                );
              })}
            </div>

            {/* Show all toggle */}
            {!teamFilter && allUpcoming.length > 6 && (
              <button
                onClick={() => setShowAllMatches(v => !v)}
                style={{
                  marginTop: 16, width: '100%',
                  background: 'none', border: '1.5px dashed var(--grey-300)', borderRadius: 8,
                  padding: '12px 0', cursor: 'pointer',
                  color: 'var(--grey-600)', fontSize: 13, fontWeight: 700, letterSpacing: '0.08em',
                  textTransform: 'uppercase',
                }}
              >
                {showAllMatches
                  ? `Show fewer`
                  : `Show all ${allUpcoming.length} upcoming matches`}
              </button>
            )}
          </div>

          <p style={{ marginTop: 20, fontSize: 12.5, color: 'var(--grey-500)', maxWidth: 600, lineHeight: 1.55 }}>
            By submitting, you confirm the bill is from your own SunFront purchase.
          </p>
        </div>
      </section>

      {/* Sponsor -- DKAA */}
      <SponsorStrip />

      {/* ── Mobile sticky CTA -- only visible when a pick is active ── */}
      {activeMatch && pick && detailsOk && !submitted.has(activeMatch.id) && isVoteable(activeMatch) && (
        <div className="sticky-cta only-mobile" style={{ bottom: 'calc(60px + env(safe-area-inset-bottom))' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="font-highlight" style={{ fontSize: 10.5, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--grey-500)' }}>Your pick</div>
              <div className="font-display" style={{ fontSize: 13, lineHeight: 1.15, marginTop: 3, color: 'var(--ink)' }}>
                {pick === 'A' ? activeMatch.teamA.name : pick === 'B' ? activeMatch.teamB.name : 'Draw'}
                {' -- '}{activeMatch.teamA.name} vs {activeMatch.teamB.name}
              </div>
            </div>
            <button
              onClick={() => submitCard(activeMatch, pick)}
              disabled={submitting === activeMatch.id}
              className="font-display btn-press"
              style={{
                background: submitting === activeMatch.id ? 'var(--grey-300)' : 'var(--orange)',
                color: '#fff', border: 'none', borderRadius: 6,
                padding: '14px 18px', fontSize: 13, letterSpacing: '0.06em', textTransform: 'uppercase',
                boxShadow: '0 4px 12px rgba(241,86,35,0.28)',
                display: 'inline-flex', alignItems: 'center', gap: 8, flexShrink: 0,
              }}
            >
              <Icon name="check" size={16} stroke={3} />
              {submitting === activeMatch.id ? '...' : 'Lock in'}
            </button>
          </div>
        </div>
      )}
    </main>
  );
}


Object.assign(window, { PredictPage });
