"use client"; import { useEffect, useMemo, useState } from "react"; import { DEMO_FORCE_OPEN, MODEL_VERSIONS } from "@/lib/mockData"; import { outcomeLabel, pct, kickoffDisplay, winProb } from "@/lib/format"; import { type Lang, dict, teamShort } from "@/lib/i18n"; import type { Outcome, CrowdStats, ModelName, Match, AIPrediction, Team, } from "@/lib/types"; import Countdown from "./Countdown"; import TeamFlag from "./TeamFlag"; type Step = "form" | "done"; const MODEL_ICON: Record = { GPT: "/icons/gpt.png", Claude: "/icons/claude.jpg", Gemini: "/icons/gemini.jpeg", }; const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; export default function Arena({ match, predictions, crowd: initialCrowd, shareUrl, lang = "ko", }: { match: Match; predictions: AIPrediction[]; crowd: CrowdStats; shareUrl: string; lang?: Lang; }) { const t = dict(lang); const aShort = teamShort(match.teamA, lang); const bShort = teamShort(match.teamB, lang); const genDate = (predictions[0]?.generatedAt ?? "").replace(/-/g, "."); const modelVersions = predictions.map((p) => MODEL_VERSIONS[p.model]).join(" · "); const [outcome, setOutcome] = useState(null); const [scoreA, setScoreA] = useState(2); const [scoreB, setScoreB] = useState(1); const [step, setStep] = useState("form"); const [email, setEmail] = useState(""); const [notify, setNotify] = useState(true); const [crowd, setCrowd] = useState(initialCrowd); const [copied, setCopied] = useState(false); // 점수 선택 시 승/무/패 자동 선택 (스코어가 결과의 소스) useEffect(() => { setOutcome( scoreA > scoreB ? "TEAM_A_WIN" : scoreA < scoreB ? "TEAM_B_WIN" : "DRAW", ); }, [scoreA, scoreB]); // 투표 창: 오픈(D-2) ≤ now < 마감(킥오프 정각). 종료 = 결과 존재. const now = Date.now(); const finished = !!match.result; const notOpen = !DEMO_FORCE_OPEN && now < new Date(match.opensAt).getTime(); const locked = now >= new Date(match.lockAt).getTime(); const disabled = finished || locked || notOpen; const ctaLabel = finished ? t.ctaResult : notOpen ? t.ctaOpens(kickoffDisplay(match.opensAt, lang)) : locked ? t.ctaLocked : t.ctaBeat; const matched = useMemo(() => { if (!outcome) return []; return predictions .filter((p) => p.outcome === outcome) .map((p) => ({ model: p.model, exact: p.scoreA === scoreA && p.scoreB === scoreB })); }, [outcome, scoreA, scoreB, predictions]); const emailValid = EMAIL_RE.test(email.trim()); const confirmSubmit = () => { if (!EMAIL_RE.test(email.trim())) return; setCrowd((c) => ({ ...c, total: c.total + 1, teamAWin: c.teamAWin + (outcome === "TEAM_A_WIN" ? 1 : 0), draw: c.draw + (outcome === "DRAW" ? 1 : 0), teamBWin: c.teamBWin + (outcome === "TEAM_B_WIN" ? 1 : 0), })); setStep("done"); }; const shareText = useMemo(() => { if (!outcome) return ""; const me = `${aShort} ${scoreA}-${scoreB} ${bShort}`; const sameAI = matched.map((m) => m.model).join("·"); if (lang === "en") { return sameAI ? `I picked ${me}, same as ${sameAI}! You? — TriplePick` : `I picked ${me} — different from all 3 AIs! You? — TriplePick`; } return sameAI ? `나는 ${me}. ${sameAI}와 같은 선택! 당신은? — TriplePick` : `나는 ${me}. AI 셋과 다 다른 선택! 당신은? — TriplePick`; }, [outcome, scoreA, scoreB, matched, aShort, bShort, lang]); const copyLink = async () => { try { await navigator.clipboard.writeText(`${shareText}\n${shareUrl}`); setCopied(true); setTimeout(() => setCopied(false), 1800); } catch { setCopied(false); } }; const nativeShare = async () => { if (navigator.share) { try { await navigator.share({ title: `${aShort} vs ${bShort} — TriplePick`, text: shareText, url: shareUrl, }); } catch { /* cancelled */ } } else copyLink(); }; return ( <> {/* ===== 카운트다운 (D7) — 투표 진행 중일 때만 ===== */} {!finished && !notOpen && (
)} {/* ===== 레이어드 화이트 시트 (002) ===== */}

{t.aiBattle}

{t.genLabel} · {genDate} 00:00 KST
{modelVersions}
{predictions.map((p) => { const wp = winProb(match, p); const wpLabel = wp.team ? t.winProb(teamShort(wp.team, lang)) : t.drawOdds; return (
{p.model}
{p.model}
{/* D3: 승리 예측 팀의 승리 확률 + 승리팀 국기 */}
{wp.team && ( )} {wpLabel} {wp.pct}%
{p.scoreA} - {p.scoreB}
{/* 메인 리즌 강조 (D3: 확률은 텍스트, 바 제거) */}

“{p.reasonShort}”

); })}
{/* 당신의 선택 */}
{t.yourPickLabel} {t.yourChoice}
{( [ ["TEAM_A_WIN", `${aShort} ${t.win}`], ["DRAW", t.drawLabel], ["TEAM_B_WIN", `${bShort} ${t.win}`], ] as [Outcome, string][] ).map(([val, label]) => ( ))}
:
{/* ===== 종료된 경기: 결과 보기 ===== */} {finished && match.result && (
{t.finalResult}
{aShort} {match.result.scoreA}-{match.result.scoreB} {bShort} {outcomeLabel(match, match.result.outcome, lang)}
{predictions.map((p) => { const hit = p.outcome === match.result!.outcome; return (
{p.model} {hit ? t.hit : t.miss}
); })}
)} {/* ===== 투표 마감/오픈 전: 상태 버튼만 ===== */} {!finished && disabled && ( )} {/* ===== 투표 제출 (D5): 이메일 + 단일 CTA를 한 카드로 통합 ===== */} {!finished && !disabled && step === "form" && (
{t.emailGate}
setEmail(e.target.value)} placeholder={t.emailPh} className="w-full rounded-xl border border-[var(--line-d)] bg-[#0f1217] px-4 py-3.5 text-[16px] text-white outline-none focus:border-[var(--green)]" />
)} {/* ===== 제출 완료: 같은 AI + 경기별 공유 (D8) ===== */} {!finished && step === "done" && outcome && (
{t.myPick}
{aShort} {scoreA}-{scoreB} {bShort} {outcomeLabel(match, outcome, lang)}

{matched.length > 0 ? t.sameAI( matched.map((m) => m.model).join("·"), matched.some((m) => m.exact), ) : t.soloPick}

)} {/* ===== 골드 상금 (003) ===== */}
₩1,000,000 Final Challenge

{t.goldDesc}

{/* ===== Crowd Pick (003) — 보팅 비율(≠ AI 승리 확률) ===== */}
Crowd Pick{" "} {t.crowdSub}
{t.joined(crowd.total.toLocaleString())}
); } function Stepper({ label, value, onChange, disabled, }: { label: string; value: number; onChange: (n: number) => void; disabled?: boolean; }) { return (
onChange(Math.max(0, value - 1))}> −
{value} {label}
= 9} onClick={() => onChange(Math.min(9, value + 1))}> +
); } function StepBtn({ children, onClick, disabled, }: { children: React.ReactNode; onClick: () => void; disabled?: boolean; }) { return ( ); } function CrowdSeg({ team, value, tone, }: { team?: Team; value: number; tone: "a" | "draw" | "b"; }) { const bg = tone === "draw" ? "#2b313c" : tone === "a" ? "rgba(74,255,160,0.22)" : "#222831"; return (
{team && } {value}%
); }