const synthEmail = u => `${String(u).trim().toLowerCase()}@${MP_EMAIL_DOMAIN}`;
const MATCH_COLS = "id,blue_user,red_user,status,turn,deadline,updated_at,blue_pool,red_pool,blue_bans,red_bans,blue_locked,red_locked,blue_lineup,red_lineup,p_blue,winner,finish_reason";
const TIER_KO = {
  Iron: "아이언",
  Silver: "실버",
  Gold: "골드",
  Platinum: "플래티넘",
  Emerald: "에메랄드",
  Diamond: "다이아몬드",
  Master: "마스터",
  Grandmaster: "그랜드마스터",
  Challenger: "챌린저"
};
function tierLabelEn(lp, rank) {
  if (lp >= 2400) {
    if (rank && rank >= 1 && rank <= 300) return "Challenger";
    if (rank && rank >= 301 && rank <= 1000) return "Grandmaster";
    return "Master";
  }
  const tiers = ["Iron", "Silver", "Gold", "Platinum", "Emerald", "Diamond"];
  const t = tiers[Math.min(5, Math.floor(lp / 400))];
  const div = 4 - Math.floor(lp % 400 / 100);
  return `${t} ${div}`;
}
function tierKo(label) {
  const [t, d] = String(label || "").split(" ");
  return (TIER_KO[t] || t || "—") + (d ? ` ${d}` : "");
}
function tierClass(label) {
  return "tier-" + String(label || "").split(" ")[0].toLowerCase();
}
function divisionLp(lp) {
  lp = lp || 0;
  return lp >= 2400 ? lp - 2400 : lp % 100;
}
function mapAuthErr(e) {
  const m = e && e.message || String(e);
  if (/already registered|User already/i.test(m)) return new Error("이미 사용 중인 아이디입니다.");
  if (/DUPLICATE_PROFILE|Database error saving new user/i.test(m)) return new Error("이미 사용 중인 아이디 또는 닉네임입니다.");
  if (/Password should be at least/i.test(m)) return new Error("비밀번호는 8자 이상이어야 합니다.");
  if (/captcha/i.test(m)) return new Error("보안 확인(CAPTCHA)에 실패했습니다. 다시 시도해 주세요.");
  if (/Invalid login credentials/i.test(m)) return new Error("아이디 또는 비밀번호가 올바르지 않습니다.");
  return new Error(m);
}
function useSession() {
  const [session, setSession] = useState(undefined);
  useEffect(() => {
    if (!sb) {
      setSession(null);
      return;
    }
    sb.auth.getSession().then(({
      data
    }) => setSession(data.session || null));
    const {
      data: sub
    } = sb.auth.onAuthStateChange((_e, s) => setSession(s || null));
    return () => {
      try {
        sub.subscription.unsubscribe();
      } catch (e) {}
    };
  }, []);
  return session;
}
function BackendNotice() {
  return <div className="panel">
      <div className="panel-title"><h3>백엔드(Supabase) 미설정</h3></div>
      <div className="small">
        멀티플레이·랭킹을 쓰려면 <code>js/supabase-config.js</code> 에 본인 Supabase 프로젝트의
        <code>URL</code> 과 <code>anon key</code> 를 넣고, <code>supabase db push</code> 로 마이그레이션을 적용하세요.
        (NPC 대전·자유 시뮬레이션은 백엔드 없이도 정상 동작합니다.)
      </div>
    </div>;
}
function CaptchaBox({
  onToken,
  resetSignal
}) {
  const ref = useRef(null);
  const widgetId = useRef(null);
  useEffect(() => {
    if (!CAPTCHA_ENABLED) return;
    let cancelled = false;
    (function tryRender() {
      if (cancelled) return;
      if (!window.turnstile || !ref.current) {
        setTimeout(tryRender, 150);
        return;
      }
      if (widgetId.current != null) return;
      widgetId.current = window.turnstile.render(ref.current, {
        sitekey: TURNSTILE_SITE_KEY,
        callback: t => onToken(t),
        "error-callback": () => onToken(""),
        "expired-callback": () => onToken("")
      });
    })();
    return () => {
      cancelled = true;
    };
  }, []);
  useEffect(() => {
    if (CAPTCHA_ENABLED && window.turnstile && widgetId.current != null && resetSignal) {
      try {
        window.turnstile.reset(widgetId.current);
      } catch (e) {}
      onToken("");
    }
  }, [resetSignal]);
  if (!CAPTCHA_ENABLED) return null;
  return <div className="cf-turnstile" ref={ref} style={{
    marginTop: 8
  }} />;
}
function AuthGate() {
  const [mode, setMode] = useState("login");
  const [username, setUsername] = useState("");
  const [nickname, setNickname] = useState("");
  const [password, setPassword] = useState("");
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState("");
  const [captchaToken, setCaptchaToken] = useState("");
  const [captchaReset, setCaptchaReset] = useState(0);
  async function submit(e) {
    e.preventDefault();
    setErr("");
    const u = username.trim();
    if (!u || !password) {
      setErr("아이디와 비밀번호를 입력하세요.");
      return;
    }
    if (/[^a-zA-Z0-9_]/.test(u)) {
      setErr("아이디는 영문/숫자/밑줄(_)만 사용하세요.");
      return;
    }
    if (mode === "signup" && !nickname.trim()) {
      setErr("닉네임을 입력하세요.");
      return;
    }
    if (mode === "signup" && password.length < 8) {
      setErr("비밀번호는 8자 이상이어야 합니다.");
      return;
    }
    if (CAPTCHA_ENABLED && !captchaToken) {
      setErr("보안 확인(CAPTCHA)을 완료해 주세요.");
      return;
    }
    const cap = CAPTCHA_ENABLED ? {
      captchaToken
    } : {};
    setBusy(true);
    try {
      if (mode === "signup") {
        const [{
          data: uOk
        }, {
          data: nOk
        }] = await Promise.all([sb.rpc("username_available", {
          p: u
        }), sb.rpc("nickname_available", {
          p: nickname.trim()
        })]);
        if (uOk === false) throw new Error("이미 사용 중인 아이디입니다.");
        if (nOk === false) throw new Error("이미 사용 중인 닉네임입니다.");
        const {
          error
        } = await sb.auth.signUp({
          email: synthEmail(u),
          password,
          options: {
            data: {
              username: u,
              nickname: nickname.trim()
            },
            ...cap
          }
        });
        if (error) throw mapAuthErr(error);
        const {
          data: s
        } = await sb.auth.getSession();
        if (!s.session) {
          const {
            error: e2
          } = await sb.auth.signInWithPassword({
            email: synthEmail(u),
            password,
            options: cap
          });
          if (e2) throw mapAuthErr(e2);
        }
      } else {
        const {
          error
        } = await sb.auth.signInWithPassword({
          email: synthEmail(u),
          password,
          options: cap
        });
        if (error) throw mapAuthErr(error);
      }
    } catch (e2) {
      setErr(e2.message || String(e2));
      if (CAPTCHA_ENABLED) {
        setCaptchaToken("");
        setCaptchaReset(n => n + 1);
      }
    } finally {
      setBusy(false);
    }
  }
  return <div className="auth-card">
      <div className="auth-tabs">
        <button className={"tab" + (mode === "login" ? " active" : "")} onClick={() => {
        setMode("login");
        setErr("");
      }}>로그인</button>
        <button className={"tab" + (mode === "signup" ? " active" : "")} onClick={() => {
        setMode("signup");
        setErr("");
      }}>회원가입</button>
      </div>
      <form className="auth-form" onSubmit={submit}>
        {mode === "signup" && <label>닉네임
            <input value={nickname} onChange={e => setNickname(e.target.value)} maxLength={16} placeholder="랭킹에 표시될 이름" />
          </label>}
        <label>아이디
          <input value={username} onChange={e => setUsername(e.target.value)} maxLength={20} autoComplete="username" placeholder="영문/숫자/_" />
        </label>
        <label>비밀번호
          <input type="password" value={password} onChange={e => setPassword(e.target.value)} autoComplete={mode === "signup" ? "new-password" : "current-password"} placeholder={mode === "signup" ? "8자 이상" : "비밀번호"} />
        </label>
        <CaptchaBox onToken={setCaptchaToken} resetSignal={captchaReset} />
        {err && <div className="auth-err">{err}</div>}
        <button className="btn btn-primary btn-lg" type="submit" disabled={busy}>
          {busy ? "처리 중…" : mode === "signup" ? "회원가입" : "로그인"}
        </button>
        {mode === "signup" && <div className="small auth-note">※ 이메일 없이 아이디로 가입합니다. 비밀번호 재설정은 지원되지 않으니 기억해 두세요.</div>}
      </form>
    </div>;
}
function RankingPage() {
  const session = useSession();
  const [rows, setRows] = useState(null);
  const [me, setMe] = useState(null);
  const [err, setErr] = useState("");
  useEffect(() => {
    if (!sb) return;
    let alive = true;
    (async () => {
      try {
        const {
          data,
          error
        } = await sb.rpc("leaderboard_top", {
          p_limit: 300
        });
        if (error) throw error;
        if (!alive) return;
        setRows(data || []);
        if (session) {
          const {
            data: mr
          } = await sb.rpc("my_rank");
          if (alive) setMe(mr && mr[0] || null);
        }
      } catch (e) {
        if (alive) setErr(e.message || String(e));
      }
    })();
    return () => {
      alive = false;
    };
  }, [session]);
  if (!MP_CONFIGURED) return <BackendNotice />;
  if (err) return <div className="panel"><div className="small">랭킹을 불러오지 못했습니다: {err}</div></div>;
  if (rows === null) return <div className="center-wait">랭킹 불러오는 중…</div>;
  const myId = session && session.user && session.user.id;
  const inTop = me && rows.some(r => r.user_id === myId);
  const Row = ({
    r,
    self
  }) => <tr className={self ? "me-row" : ""}>
      <td className="rk">{r.rank}</td>
      <td><span className={"tier-badge " + tierClass(r.tier)}>{tierKo(r.tier)}</span></td>
      <td className="nick">{r.nickname}{self ? " (나)" : ""}</td>
      <td className="lp">{divisionLp(r.lp)} LP</td>
      <td className="wl">{r.wins}승 {r.losses}패 {r.draws}무</td>
    </tr>;
  return <div className="rank-wrap">
      <div className="panel-title"><h3>랭킹 (Top 300)</h3></div>
      {me && !inTop && <table className="rank-table self-rank">
          <tbody><Row r={{
          ...me,
          user_id: myId
        }} self /></tbody>
        </table>}
      <table className="rank-table">
        <thead><tr><th>순위</th><th>티어</th><th>닉네임</th><th>LP</th><th>전적</th></tr></thead>
        <tbody>
          {rows.length === 0 && <tr><td colSpan={5} className="small">아직 랭킹 데이터가 없습니다. 첫 대전을 진행해 보세요.</td></tr>}
          {rows.map(r => <Row key={r.user_id} r={r} self={r.user_id === myId} />)}
        </tbody>
      </table>
    </div>;
}
function MultiplayerPage({
  eng
}) {
  const session = useSession();
  const [phase, setPhase] = useState("idle");
  const [match, setMatch] = useState(null);
  const [profile, setProfile] = useState(null);
  const [myRank, setMyRank] = useState(null);
  async function reload() {
    if (!sb || !session) return;
    const {
      data: p
    } = await sb.from("profiles").select("id,nickname,wins,losses,draws,lp,created_at,updated_at").eq("id", session.user.id).maybeSingle();
    setProfile(p || null);
    const {
      data: mr
    } = await sb.rpc("my_rank");
    setMyRank(mr && mr[0] || null);
  }
  useEffect(() => {
    reload();
  }, [session]);
  if (!MP_CONFIGURED) return <BackendNotice />;
  if (session === undefined) return <div className="center-wait">확인 중…</div>;
  if (!session) {
    return <div className="mp-gate">
        <div className="small mp-gate-msg">멀티플레이와 랭킹은 로그인이 필요합니다.</div>
        <AuthGate />
      </div>;
  }
  if (phase === "match" && match) {
    return <MultiplayerDraft eng={eng} session={session} matchId={match.id} side={match.side} onLeave={() => {
      setMatch(null);
      setPhase("idle");
      reload();
    }} />;
  }
  if (phase === "searching") {
    return <Matchmaking session={session} onMatched={(id, side) => {
      setMatch({
        id,
        side
      });
      setPhase("match");
    }} onCancel={() => setPhase("idle")} />;
  }
  return <div className="mp-landing">
      <div className="mp-profile">
        <div className="mp-id">
          <span className="mp-nick">{profile ? profile.nickname : "…"}</span>
          {myRank && <span className={"tier-badge " + tierClass(myRank.tier)}>{tierKo(myRank.tier)} · {divisionLp(myRank.lp)} LP</span>}
        </div>
        {profile && <div className="small mp-record">{profile.wins}승 {profile.losses}패 {profile.draws}무{myRank ? ` · 현재 ${myRank.rank}위` : ""}</div>}
        <button className="btn-link mp-logout" onClick={() => sb.auth.signOut()}>로그아웃</button>
      </div>
      <div className="mp-start">
        <div className="mp-start-msg">무작위 상대와 1 vs 1 밴픽 대전을 진행합니다. 진영(Blue/Red)은 자동 배정되며, 각 수는 30초 제한이 있습니다.</div>
        <button className="btn btn-primary btn-lg" onClick={() => setPhase("searching")}>대전 시작 ▶</button>
        <div className="small mp-warn">※ 진행 중 페이지를 닫으면 <b>패배</b> 처리됩니다. 예상 승률은 게임이 끝나야 공개됩니다.</div>
      </div>
    </div>;
}
function Matchmaking({
  session,
  onMatched,
  onCancel
}) {
  const uid = session.user.id;
  const [err, setErr] = useState("");
  const doneRef = useRef(false);
  useEffect(() => {
    let channel = null,
      poll = null;
    async function finish(matchId) {
      if (doneRef.current) return;
      doneRef.current = true;
      cleanup();
      let side = "blue";
      try {
        const {
          data: m
        } = await sb.from("matches").select("blue_user").eq("id", matchId).single();
        side = m && m.blue_user === uid ? "blue" : "red";
      } catch (e) {}
      onMatched(matchId, side);
    }
    function cleanup() {
      if (channel) {
        try {
          sb.removeChannel(channel);
        } catch (e) {}
        channel = null;
      }
      if (poll) {
        clearInterval(poll);
        poll = null;
      }
    }
    async function go() {
      try {
        const {
          data,
          error
        } = await sb.rpc("mm_enqueue");
        if (error) throw error;
        const row = data && data[0];
        if (row && row.match_id) {
          finish(row.match_id);
          return;
        }
        channel = sb.channel("mmq:" + uid).on("postgres_changes", {
          event: "UPDATE",
          schema: "public",
          table: "matchmaking_queue",
          filter: "user_id=eq." + uid
        }, payload => {
          const mid = payload.new && payload.new.match_id;
          if (mid) finish(mid);
        }).subscribe();
        poll = setInterval(async () => {
          const {
            data: d2
          } = await sb.rpc("mm_enqueue");
          const r2 = d2 && d2[0];
          if (r2 && r2.match_id) {
            finish(r2.match_id);
            return;
          }
          const {
            data: q
          } = await sb.from("matchmaking_queue").select("match_id").eq("user_id", uid).maybeSingle();
          if (q && q.match_id) finish(q.match_id);
        }, 1800);
      } catch (e) {
        setErr(e.message || String(e));
      }
    }
    go();
    return () => {
      cleanup();
      if (!doneRef.current) {
        try {
          sb.from("matchmaking_queue").delete().eq("user_id", uid);
        } catch (e) {}
      }
    };
  }, []);
  async function cancel() {
    doneRef.current = true;
    try {
      await sb.from("matchmaking_queue").delete().eq("user_id", uid);
    } catch (e) {}
    onCancel();
  }
  return <div className="mp-search">
      <div className="mp-spinner" />
      <div className="mp-search-msg">상대를 찾는 중…</div>
      {err && <div className="auth-err">{err}</div>}
      <button className="btn btn-secondary" onClick={cancel}>취소</button>
    </div>;
}
function MultiplayerDraft({
  eng,
  session,
  matchId,
  side,
  onLeave
}) {
  const uid = session.user.id;
  const [match, setMatch] = useState(null);
  const [names, setNames] = useState({
    blue: "Blue",
    red: "Red"
  });
  const [query, setQuery] = useState("");
  const [cat, setCat] = useState("all");
  const [pending, setPending] = useState(null);
  const [myAssign, setMyAssign] = useState({});
  const [now, setNow] = useState(Date.now());
  const [msg, setMsg] = useState("");
  const myAssignRef = useRef({});
  const firedRef = useRef(null);
  const settleRef = useRef(false);
  const afkRef = useRef(null);
  useEffect(() => {
    myAssignRef.current = myAssign;
  }, [myAssign]);
  function mergeMatch(row) {
    if (!row) return;
    setMatch(prev => {
      if (prev && row.updated_at && prev.updated_at && Date.parse(row.updated_at) < Date.parse(prev.updated_at)) return prev;
      return row;
    });
  }
  useEffect(() => {
    let ch = null,
      alive = true;
    (async () => {
      const {
        data: m
      } = await sb.from("matches").select(MATCH_COLS).eq("id", matchId).single();
      if (!alive) return;
      setMatch(m);
      if (!m) return;
      try {
        const {
          data: profs
        } = await sb.from("profiles").select("id,nickname").in("id", [m.blue_user, m.red_user]);
        const nm = {
          blue: "Blue",
          red: "Red"
        };
        (profs || []).forEach(p => {
          if (p.id === m.blue_user) nm.blue = p.nickname;
          if (p.id === m.red_user) nm.red = p.nickname;
        });
        if (alive) setNames(nm);
      } catch (e) {}
      const oppId = side === "blue" ? m.red_user : m.blue_user;
      ch = sb.channel("match:" + matchId, {
        config: {
          presence: {
            key: uid
          }
        }
      });
      ch.on("postgres_changes", {
        event: "UPDATE",
        schema: "public",
        table: "matches",
        filter: "id=eq." + matchId
      }, payload => mergeMatch(payload.new));
      ch.on("presence", {
        event: "leave"
      }, ({
        key
      }) => {
        if (key === oppId) afkRef.current = Date.now();
      });
      ch.on("presence", {
        event: "join"
      }, ({
        key
      }) => {
        if (key === oppId) afkRef.current = null;
      });
      ch.subscribe(st => {
        if (st === "SUBSCRIBED") ch.track({
          uid
        });
      });
    })();
    return () => {
      alive = false;
      if (ch) {
        try {
          sb.removeChannel(ch);
        } catch (e) {}
      }
    };
  }, [matchId]);
  useEffect(() => {
    const ping = () => {
      sb.rpc("match_ping", {
        p_match: matchId
      }).then(undefined, () => {});
    };
    const pollState = () => {
      sb.from("matches").select(MATCH_COLS).eq("id", matchId).single().then(({
        data
      }) => mergeMatch(data)).catch(() => {});
    };
    ping();
    pollState();
    const hp = setInterval(ping, 5000);
    const hs = setInterval(pollState, 15000);
    return () => {
      clearInterval(hp);
      clearInterval(hs);
    };
  }, [matchId]);
  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 250);
    return () => clearInterval(t);
  }, []);
  useEffect(() => {
    const onHide = () => {
      try {
        sb.rpc("match_forfeit", {
          p_match: matchId
        });
      } catch (e) {}
    };
    window.addEventListener("pagehide", onHide);
    window.addEventListener("beforeunload", onHide);
    return () => {
      window.removeEventListener("pagehide", onHide);
      window.removeEventListener("beforeunload", onHide);
    };
  }, [matchId]);
  useEffect(() => {
    if (match && match.status === "swapping" && Object.keys(myAssign).length === 0) {
      const blue = {
        pool: match.blue_pool || [],
        bans: match.blue_bans || []
      };
      const red = {
        pool: match.red_pool || [],
        bans: match.red_bans || []
      };
      const sub = eng.solveSubgame(blue, red);
      setMyAssign({
        ...(side === "blue" ? sub.blueLineup : sub.redLineup)
      });
    }
  }, [match && match.status]);
  useEffect(() => {
    if (!match) return;
    const st = match.status;
    if ((st === "drafting" || st === "swapping") && afkRef.current && Date.now() - afkRef.current > 12000) {
      afkRef.current = null;
      sb.rpc("match_report_afk", {
        p_match: matchId
      }).then(undefined, () => {});
    }
    if (st === "swapping" && match.blue_locked && match.red_locked && !settleRef.current) {
      settleRef.current = true;
      sb.functions.invoke("settle_match", {
        body: {
          match_id: matchId
        }
      }).catch(() => {});
    }
    if (st === "drafting" || st === "swapping") {
      const dl = match.deadline ? Date.parse(match.deadline) : null;
      if (dl && now > dl && firedRef.current !== match.deadline) {
        firedRef.current = match.deadline;
        if (st === "drafting") {
          sb.rpc("match_timeout", {
            p_match: matchId
          }).then(undefined, () => {});
        } else {
          (async () => {
            if (!match[side + "_locked"]) {
              try {
                await sb.rpc("match_lock_lineup", {
                  p_match: matchId,
                  p_lineup: myAssignRef.current || {}
                });
              } catch (e) {}
            }
            if (!settleRef.current) {
              settleRef.current = true;
              try {
                await sb.functions.invoke("settle_match", {
                  body: {
                    match_id: matchId
                  }
                });
              } catch (e) {}
            }
          })();
        }
      }
    }
  }, [match, now]);
  const status = match ? match.status : null;
  const bluePool = match && match.blue_pool || [];
  const redPool = match && match.red_pool || [];
  const blueBans = match && match.blue_bans || [];
  const redBans = match && match.red_bans || [];
  const turn = match ? match.turn : 0;
  const slot = status === "drafting" ? ORDER[turn] : null;
  const isMyTurn = !!slot && slot.side === side;
  const myPool = side === "blue" ? bluePool : redPool;
  const oppSide = side === "blue" ? "red" : "blue";
  const iLocked = match ? !!match[side + "_locked"] : false;
  const oppLocked = match ? !!match[oppSide + "_locked"] : false;
  const dl = match && match.deadline ? Date.parse(match.deadline) : null;
  const secsLeft = dl ? Math.max(0, Math.ceil((dl - now) / 1000)) : null;
  const candidates = useMemo(() => {
    if (!isMyTurn) return [];
    const picked = new Set([...bluePool, ...redPool]);
    const banned = new Set([...blueBans, ...redBans]);
    let rows = eng.ALLKEYS.map(k => {
      const c = eng.CH[k];
      return {
        k,
        name: c.name,
        pos: c.positions || [],
        wr: c.base_wr,
        banned: banned.has(k),
        picked: picked.has(k)
      };
    });
    if (cat !== "all") rows = rows.filter(r => r.pos.includes(cat));
    rows.sort((a, b) => a.name.localeCompare(b.name, "ko"));
    const q = query.trim().toLowerCase();
    return q ? rows.filter(r => r.name.toLowerCase().includes(q) || r.k.includes(q)) : rows;
  }, [match, query, cat, isMyTurn]);
  function applyPending(next) {
    setPending(next);
    sb.rpc("match_set_pending", {
      p_match: matchId,
      p_champ: next
    }).then(undefined, () => {});
  }
  async function confirmPick(k) {
    setPending(null);
    setQuery("");
    try {
      const {
        error
      } = await sb.rpc("match_move", {
        p_match: matchId,
        p_champ: k
      });
      if (error) throw error;
    } catch (e) {
      setMsg("선택이 반영되지 않았습니다(이미 사용됐거나 차례가 지났습니다).");
      setTimeout(() => setMsg(""), 2500);
    }
  }
  function setAssign(pos, key) {
    setMyAssign(prev => {
      const a = {
        ...prev
      };
      for (const pp of POSITIONS) if (a[pp] === key) delete a[pp];
      if (key) a[pos] = key;else delete a[pos];
      return a;
    });
  }
  function autoAssign() {
    const blue = {
        pool: bluePool,
        bans: blueBans
      },
      red = {
        pool: redPool,
        bans: redBans
      };
    const sub = eng.solveSubgame(blue, red);
    setMyAssign({
      ...(side === "blue" ? sub.blueLineup : sub.redLineup)
    });
  }
  async function lockLineup() {
    try {
      const {
        error
      } = await sb.rpc("match_lock_lineup", {
        p_match: matchId,
        p_lineup: myAssign
      });
      if (error) throw error;
    } catch (e) {
      setMsg("배치 잠금 실패: " + (e.message || e));
      setTimeout(() => setMsg(""), 2500);
    }
  }
  async function leave() {
    if (status === "drafting" || status === "swapping") {
      if (!window.confirm("지금 나가면 패배 처리됩니다. 나가시겠습니까?")) return;
      try {
        await sb.rpc("match_forfeit", {
          p_match: matchId
        });
      } catch (e) {}
    }
    onLeave();
  }
  if (!match) return <div className="center-wait">대전 불러오는 중…</div>;
  const finished = status === "finished" || status === "aborted";
  let outcome = null;
  if (status === "aborted") outcome = "abort";else if (status === "finished") outcome = match.finish_reason === "draw" ? "draw" : match.winner === side ? "win" : "lose";
  const reveal = status === "finished" && (match.finish_reason === "winrate" || match.finish_reason === "draw") && match.p_blue != null;
  const assignFilled = POSITIONS.filter(pp => myAssign[pp]).length;
  function BanRow(s) {
    const bans = s === "blue" ? blueBans : redBans;
    const active = !!slot && slot.type === "ban" && slot.side === s;
    const previewKey = active && s === side && pending ? pending : null;
    return <div className={`ban-side ${s}`}>
        {Array.from({
        length: 5
      }).map((_, i) => {
        const filledK = bans[i];
        const isPreview = !!previewKey && !filledK && i === bans.length;
        const k = filledK || (isPreview ? previewKey : null);
        const c = eng && k ? eng.CH[k] : null;
        const cur = active && i === bans.length;
        return <span key={i} className={`ban-slot${filledK ? " filled" : ""}${cur ? " cur" : ""}${isPreview ? " preview" : ""}`} title={c ? c.name : undefined}>
              <ChampPortrait champKey={k || null} name={c ? c.name : null} size={48} variant="ban-portrait" />
            </span>;
      })}
      </div>;
  }
  function PickCol(s) {
    const pool = s === "blue" ? bluePool : redPool;
    const isMe = s === side;
    const headName = names[s] || (s === "blue" ? "BLUE" : "RED");
    if (status === "drafting") {
      const active = !!slot && slot.side === s;
      const inactive = !!slot && slot.side !== s;
      const banTurn = active && slot.type === "ban";
      const previewKey = slot && slot.type === "pick" && slot.side === s && s === side && pending ? pending : null;
      return <div className={`pick-col ${s}${active ? " active" : ""}${banTurn ? " ban-turn" : ""}${inactive ? " inactive" : ""}`}>
          <div className={`pick-col-head ${s}`}>{headName}{isMe ? " (나)" : ""}</div>
          {Array.from({
          length: 5
        }).map((_, i) => {
          const filledK = pool[i];
          const isPreview = !!previewKey && !filledK && i === pool.length;
          const k = filledK || (isPreview ? previewKey : null);
          const c = eng && k ? eng.CH[k] : null;
          return <div key={i} className={`pick-slot ${s}${filledK ? " filled" : ""}${isPreview ? " preview" : ""}`}>
                <ChampPortrait champKey={k || null} name={c ? c.name : null} size={60} />
                <span className="pick-name">{isPreview ? "선택 중" : c ? c.name : "—"}</span>
              </div>;
        })}
        </div>;
    }
    const showAssign = status !== "swapping" || isMe || reveal;
    if (status === "swapping" && !isMe && !reveal) {
      return <div className={`pick-col ${s}`}>
          <div className={`pick-col-head ${s}`}>{headName}<span className="col-sub">{oppLocked ? "배치 완료" : "라인 배치 중…"}</span></div>
          {Array.from({
          length: 5
        }).map((_, i) => {
          const k = pool[i];
          const c = eng && k ? eng.CH[k] : null;
          return <div key={i} className={`pick-slot ${s}${c ? " filled" : ""}`}>
                <ChampPortrait champKey={k || null} name={c ? c.name : null} size={60} />
                <span className="pick-name">{c ? c.name : "—"}</span>
              </div>;
        })}
        </div>;
    }
    const assign = reveal ? (s === "blue" ? match.blue_lineup : match.red_lineup) || {} : myAssign;
    const editable = status === "swapping" && isMe && !reveal;
    return <div className={`pick-col ${s}`}>
        <div className={`pick-col-head ${s}`}>{headName}{isMe ? " (나)" : ""}</div>
        {POSITIONS.map(pos => {
        const k = assign ? assign[pos] : null;
        const c = eng && k ? eng.CH[k] : null;
        if (editable) {
          const usedElsewhere = new Set(POSITIONS.filter(pp => pp !== pos).map(pp => myAssign[pp]).filter(Boolean));
          const opts = myPool.filter(key => !usedElsewhere.has(key));
          return <div key={pos} className={`pick-slot ${s}${c ? " filled" : ""}`}>
                <ChampPortrait champKey={k || null} name={c ? c.name : null} size={60} />
                <span className="pos-tag">{ROLE_KR[pos]}</span>
                <select className="swap-select" value={k || ""} disabled={iLocked} onChange={e => setAssign(pos, e.target.value || null)}>
                  <option value="">—</option>
                  {opts.map(key => <option key={key} value={key}>{eng.CH[key].name}</option>)}
                </select>
              </div>;
        }
        return <div key={pos} className={`pick-slot ${s}${c ? " filled" : ""}`}>
              <ChampPortrait champKey={k || null} name={c ? c.name : null} size={60} />
              <span className="pos-tag">{ROLE_KR[pos]}</span>
              <span className="pick-name">{c ? c.name : "—"}</span>
            </div>;
      })}
      </div>;
  }
  return <div className="draft mp-draft">
      <div className="draft-topbar">
        <span className="eyebrow">멀티 대전 · 나는 {side === "blue" ? "Blue" : "Red"}</span>
        <span style={{
        display: "flex",
        gap: 8,
        alignItems: "center"
      }}>
          {!finished && secsLeft != null && <span className={"mp-timer" + (secsLeft <= 10 ? " urgent" : "")}>{secsLeft}s</span>}
          <button className="btn btn-secondary" onClick={leave}>{finished ? "나가기" : "그만두기"}</button>
        </span>
      </div>

      {msg && <div className="mp-toast">{msg}</div>}

      <div className={"ban-bar" + (slot && slot.type === "ban" ? " banning" : "")}>
        {BanRow("blue")}
        <div className="ban-vs">BANS</div>
        {BanRow("red")}
      </div>

      <div className="draft-main">
        {PickCol("blue")}
        <div className="center-col">
          {status === "drafting" ? isMyTurn ? <ChampPicker kind={slot.type} cat={cat} setCat={setCat} query={query} setQuery={setQuery} rows={candidates} onPick={confirmPick} pending={pending} setPending={applyPending} note={slot.type === "ban" ? "⊘ 밴 단계 — 상대가 쓰지 못하도록 금지할 챔피언을 고르세요. (30초)" : "✓ 픽 단계 — 우리 팀이 쓸 챔피언을 고르세요. (30초)"} /> : <div className="center-wait">상대가 {slot && slot.type === "ban" ? "밴" : "픽"} 중…</div> : status === "swapping" ? <div className="center-empty">
              {iLocked ? "상대의 배치를 기다리는 중…" : "5장을 각 라인에 배치하고 확정하세요. (30초)"}
            </div> : <div className={"mp-result " + (outcome || "")}>
              <div className="mp-result-big">
                {outcome === "win" ? "승리" : outcome === "lose" ? "패배" : outcome === "draw" ? "무승부" : "무효"}
              </div>
              <div className="small">
                {match.finish_reason === "forfeit" ? outcome === "win" ? "상대가 나가서 승리했습니다." : "탈주로 패배했습니다." : match.finish_reason === "double_disconnect" ? "양측 이탈로 무효 처리되었습니다." : match.finish_reason === "draw" ? "예상 승률이 정확히 동률입니다." : "예상 승률이 더 높은 쪽이 승리합니다."}
              </div>
            </div>}
        </div>
        {PickCol("red")}
      </div>

      {}
      <div className="draft-status">
        <div className="status-main">
          {reveal ? <WinGauge p={match.p_blue} /> : <div className="gauge idle mp-hidden">
              <div className="seg-blue" style={{
            width: "50%"
          }}>{finished ? "—" : "예상 승률은 종료 후 공개"}</div>
              <div className="seg-red">{finished ? "—" : ""}</div>
            </div>}
        </div>
        <div className="status-controls">
          {status === "swapping" && !reveal && <React.Fragment>
              <button className="btn btn-secondary" disabled={iLocked} onClick={autoAssign}>추천 배치</button>
              <button className="btn btn-primary btn-lg" disabled={iLocked || assignFilled !== 5} onClick={lockLineup}>
                {iLocked ? "확정됨 ✓" : "배치 확정 ▶"}
              </button>
              {!iLocked && assignFilled !== 5 && <span className="small">모든 라인을 채우면 확정 ({assignFilled}/5)</span>}
            </React.Fragment>}
          {finished && <button className="btn btn-primary" onClick={onLeave}>로비로</button>}
        </div>
      </div>

      {reveal && <WinExplain eng={eng} blue={{
      picks: match.blue_lineup || {},
      bans: blueBans
    }} red={{
      picks: match.red_lineup || {},
      bans: redBans
    }} p={match.p_blue} assigned />}
    </div>;
}
