/* Tarot Battle UI Kit — Tactical Battle (engine-driven, multiplayer-ready) */

const SQUAD_A = ["the-emperor", "strength", "the-chariot", "ace-of-wands", "the-empress"];
const SQUAD_B = ["death", "the-devil", "the-tower", "king-of-swords", "the-star"];
const SQUAD_MAX = 5;
function cardsFor(ids) { return ids.map((id) => window.TB_UNITS.find((u) => u.id === id)); }

// Your chosen battle squad persists across sessions and is drawn from the cards
// you OWN (and ideally your saved deck), falling back to a preset.
function loadBattleSquad() {
  const owned = (window.TBProfile ? window.TBProfile.owned() : []) || [];
  const ownedSet = new Set(owned);
  try { const s = JSON.parse(localStorage.getItem("tb-squad")); if (Array.isArray(s) && s.length) { const f = s.filter((id) => ownedSet.has(id)); if (f.length) return f.slice(0, SQUAD_MAX); } } catch (e) {}
  const deck = (window.TBProfile ? window.TBProfile.deck() : []) || [];
  if (deck.length) return deck.slice(0, SQUAD_MAX);
  if (owned.length) return owned.slice(0, SQUAD_MAX);
  return SQUAD_A.slice();
}
function saveBattleSquad(ids) { try { localStorage.setItem("tb-squad", JSON.stringify(ids)); } catch (e) {} }

const SQUAD_FILTERS = [
  { id: "all", label: "All" }, { id: "major", label: "Major" },
  { id: "cups", label: "Cups" }, { id: "swords", label: "Swords" },
  { id: "wands", label: "Wands" }, { id: "pentacles", label: "Pentacles" }
];

/* ---------- Squad editor: pick your five (from owned cards) ---------- */
function SquadEditor({ squad, setSquad }) {
  const [filter, setFilter] = React.useState("all");
  const owned = new Set((window.TBProfile ? window.TBProfile.owned() : []) || []);
  const ownedUnits = window.TB_UNITS.filter((u) => owned.has(u.id));
  const pool = filter === "all" ? ownedUnits : ownedUnits.filter((u) => u.arcana === filter);
  function toggle(id) {
    setSquad((s) => {
      if (s.includes(id)) return s.filter((x) => x !== id);
      if (s.length >= SQUAD_MAX) { tbToast(`A squad is ${SQUAD_MAX} units.`, { kind: "danger", title: "Full" }); return s; }
      return [...s, id];
    });
  }
  return (
    <div className="squad-editor">
      <div className="se-slots">
        {Array.from({ length: SQUAD_MAX }).map((_, i) => {
          const id = squad[i]; const u = id && window.TB_UNITS.find((x) => x.id === id);
          return (
            <div key={i} className={"se-slot" + (u ? " filled" : "")} onClick={() => u && toggle(u.id)} title={u ? "Remove " + u.name : "Empty"}>
              {u ? <TarotCard unit={u} flippable={false} flipped={false} /> : <span className="se-plus"><Icon name="Plus" size={20} /></span>}
            </div>
          );
        })}
      </div>
      <div className="filters" style={{ margin: "14px 0 12px" }}>
        {SQUAD_FILTERS.map((f) => (
          <button key={f.id} className={"filter" + (filter === f.id ? " active" : "")} onClick={() => setFilter(f.id)}>
            {f.id !== "all" && <span style={{ color: filter === f.id ? "inherit" : window.TB_SUITS[f.id].color }}>{window.TB_SUITS[f.id].glyph}</span>}{f.label}
          </button>
        ))}
      </div>
      <div className="se-pool">
        {pool.map((u) => (
          <div key={u.id} className={"se-pcard" + (squad.includes(u.id) ? " in" : "")} onClick={() => toggle(u.id)} title={u.name}>
            <TarotCard unit={u} flippable={false} flipped={false} />
          </div>
        ))}
      </div>
    </div>
  );
}

/* ---------- Lobby: choose how to play ---------- */
function BattleLobby({ onStart }) {
  const [mode, setMode] = React.useState("ai");
  const [brain, setBrain] = React.useState("greedy");
  const [room, setRoom] = React.useState("");
  const [name, setName] = React.useState("Player");
  const [serverUrl, setServerUrl] = React.useState(() => { try { return localStorage.getItem("tb-server") || window.TBNet.defaultServerUrl(); } catch (e) { return ""; } });
  const [squad, setSquad] = React.useState(loadBattleSquad);
  const [editing, setEditing] = React.useState(false);
  const [zoomUnit, setZoomUnit] = React.useState(null);   // tap a squad card to inspect it full-size
  React.useEffect(() => { saveBattleSquad(squad); }, [squad]);
  // Deep link: a friend's shared link (…/?room=CODE) prefills Online + the code.
  React.useEffect(() => {
    try {
      const p = new URLSearchParams(window.location.search).get("room");
      if (p) {
        setMode("online");
        setRoom(p.toUpperCase().slice(0, 6));
        const d = window.TBNet.defaultServerUrl();
        if (d) setServerUrl(d);
      }
    } catch (e) {}
  }, []);
  const genCode = () => Math.random().toString(36).slice(2, 7).toUpperCase();

  const modes = [
    { id: "ai", icon: "Bot", t: "vs AI", s: "Play side A against a simple tactical AI." },
    { id: "hotseat", icon: "Users", t: "Hot-seat", s: "Two players share this screen, turn by turn." },
    { id: "online", icon: "Globe", t: "Online", s: "Play a friend over the network via a room code." }
  ];

  function start() {
    if (squad.length < 1) { tbToast("Pick at least one unit.", { kind: "danger" }); return; }
    if (mode === "online") {
      // When served from the Worker, same-origin always wins over any stale saved URL.
      const url = (window.TBNet.defaultServerUrl() || serverUrl).trim();
      try { localStorage.setItem("tb-server", url); } catch (e) {}
      const code = room.trim().toUpperCase() || genCode();
      onStart({ mode: "online", room: code, name, serverUrl: url, create: !room.trim(), squad });
    } else {
      onStart({ mode, squad, brain });
    }
  }

  return (
    <div className="lobby">
      <div className="screen-head"><div className="eyebrow">Take the field</div><h1>New Battle</h1>
        <p>Up to five units per side on a 7×6 board of forest, water and mountain. First, <b>deploy</b> your squad across your back rows; then spend action points to move, strike, and trigger abilities. Hold the golden <b>Sigils</b> for bonus action points; eliminate every enemy unit to win.</p></div>

      <div className="mode-grid">
        {modes.map((m) => (
          <button key={m.id} className={"mode" + (mode === m.id ? " sel" : "")} onClick={() => setMode(m.id)}>
            <span className="mi"><Icon name={m.icon} size={22} /></span>
            <span className="mt">{m.t}</span>
            <span className="ms">{m.s}</span>
          </button>
        ))}
      </div>

      {mode === "ai" && (
        <div className="brain-row">
          <span className="brain-label">Opponent brain</span>
          <div className="brain-toggle">
            <button className={"brain-opt" + (brain === "greedy" ? " on" : "")} onClick={() => setBrain("greedy")}><Icon name="Zap" size={14} /> Greedy <span>instant · rules-based</span></button>
            <button className={"brain-opt" + (brain === "llm" ? " on" : "")} onClick={() => setBrain("llm")}><Icon name="Brain" size={14} /> Smart (AI) <span>an LLM plans each turn</span></button>
          </div>
        </div>
      )}

      {mode === "online" && (() => {
        const sameOrigin = !!window.TBNet.defaultServerUrl();
        return (
          <div>
            <div className="online-row">
              <div className="field"><label>Your name</label><input value={name} onChange={(e) => setName(e.target.value)} style={{ fontFamily: "var(--font-body)", letterSpacing: 0 }} /></div>
              <div className="field"><label>Room code</label><input value={room} onChange={(e) => setRoom(e.target.value.toUpperCase())} placeholder="blank = create" maxLength={6} /></div>
              {!sameOrigin && <div className="field" style={{ flex: 1, minWidth: 220 }}><label>Server URL (Cloudflare Worker)</label><input value={serverUrl} onChange={(e) => setServerUrl(e.target.value)} placeholder="tarot-battle.you.workers.dev" style={{ fontFamily: "var(--font-body)", letterSpacing: 0, width: "100%" }} /></div>}
            </div>
            <p className="conn-note" style={{ marginTop: 12 }}>{sameOrigin
              ? <React.Fragment>Leave the room code blank to <b>create</b> a match and get a shareable link, or enter a friend's code to <b>join</b>.</React.Fragment>
              : <React.Fragment>Leave the room code blank to <b>create</b> a match, or enter a friend's code to <b>join</b>. Paste your deployed Worker URL (see <code>server/README.md</code>); it's remembered.</React.Fragment>}</p>
          </div>
        );
      })()}

      <div className="squad-head">
        <div>
          <div className="eyebrow" style={{ marginBottom: 4 }}>Your squad · {squad.length}/{SQUAD_MAX}</div>
          <div style={{ fontSize: 12, color: "var(--fg-3)" }}>{squad.map((id) => (window.TB_UNITS.find((u) => u.id === id) || {}).name).filter(Boolean).join(" · ") || "No units chosen"}</div>
        </div>
        <Btn kind="ghost" icon={editing ? "Check" : "Pencil"} onClick={() => setEditing((v) => !v)}>{editing ? "Done" : "Edit squad"}</Btn>
      </div>

      {editing
        ? <SquadEditor squad={squad} setSquad={setSquad} />
        : <div className="squad-preview">{cardsFor(squad).map((u) => <div className="sp" key={u.id} onClick={() => setZoomUnit(u)} title={"Inspect " + u.name}><TarotCard unit={u} flippable={false} flipped={false} /></div>)}</div>}

      <div className="lobby-cta">
        <Btn kind="primary" icon="Swords" onClick={start} disabled={mode === "online" && !serverUrl.trim() && !window.TBNet.defaultServerUrl()}>
          {mode === "online" ? (room.trim() ? "Join Room" : "Create Room") : "Start Battle"}
        </Btn>
        {mode !== "online" && <span className="conn-note">Opponent fields a preset squad: Death · Devil · Tower · King of Swords · Star.</span>}
      </div>

      {zoomUnit && <CardZoom unit={zoomUnit} onClose={() => setZoomUnit(null)} />}
    </div>
  );
}

/* ---------- A single board token ---------- */
function BoardToken({ state, unit, selected, onClick, flash, floats }) {
  const E = window.TBEngine;
  const eff = E.effective(state, unit);
  const buff = (eff.atk - unit.a) + (eff.def - unit.d);
  const spent = unit.owner === state.turn.side && unit.movedThisTurn && unit.actedThisTurn;
  const ST = (window.TB_GLOSSARY || {}).STATUS || {};
  const statuses = (unit.statuses || []);
  return (
    <div className={"token " + (unit.owner === "A" ? "player" : "enemy") + (selected ? " sel" : "") + (spent ? " spent" : "") + (flash ? " fx-" + flash : "")}
         style={{ backgroundImage: `url('${unit.img}')` }} onClick={onClick} title={unit.name}>
      <span className="side-bar" />
      <span className="tok-atk">{eff.atk}</span>
      {buff !== 0 && <span className="tok-buff">{buff > 0 ? "+" + buff : buff}</span>}
      {statuses.length > 0 && (
        <span className="tok-status">
          {statuses.slice(0, 3).map((s, i) => {
            const m = ST[s.k] || { icon: "Circle", tone: "neutral" };
            return <i key={i} className={"ts " + m.tone}><Icon name={m.icon} size={10} /></i>;
          })}
        </span>
      )}
      <span className="tok-hp">{Array.from({ length: unit.maxHp }).map((_, i) => <i key={i} className={i < unit.hp ? "" : "empty"} />)}</span>
      {(floats || []).map((f) => <span key={f.key} className={"combat-float " + f.kind}>{f.text}</span>)}
    </div>
  );
}

/* ---------- Live board ---------- */
function BoardView({ session, onExit }) {
  const E = window.TBEngine;
  const [, force] = React.useReducer((x) => x + 1, 0);
  const [sel, setSel] = React.useState(null);
  const [zoom, setZoom] = React.useState(null);   // a unit being inspected full-size
  const [benchSel, setBenchSel] = React.useState(null);
  const [abilityMode, setAbilityMode] = React.useState(null); // holds the selected ability id, or null
  const [myRole, setMyRole] = React.useState(session.role());
  const [conn, setConn] = React.useState(session.kind === "online" ? "connecting" : "open");
  const [lobby, setLobby] = React.useState({ a: null, b: null });
  const [reward, setReward] = React.useState(null);
  const rewardedRef = React.useRef(false);
  const [thinking, setThinking] = React.useState(false);
  const [fxFlash, setFxFlash] = React.useState({});   // unitId → 'hit' | 'heal'
  const [fxFloats, setFxFloats] = React.useState([]); // {key, unitId, kind, text}
  const prevStateRef = React.useRef(null);

  // derive combat FX (floating numbers + flashes) from each state diff
  function diffFx(prev, next) {
    if (!prev || !next || next.phase !== "battle") { prevStateRef.current = next; return; }
    const pm = {}; prev.units.forEach((u) => { pm[u.id] = u; });
    const flash = {}; const floats = [];
    next.units.forEach((u) => {
      const p = pm[u.id]; if (!p) return;
      if (u.hp < p.hp) { flash[u.id] = "hit"; floats.push({ key: u.id + "-" + next.turn.n + "-" + Math.random().toString(36).slice(2, 6), unitId: u.id, kind: "dmg", text: "−" + (p.hp - u.hp) }); }
      else if (u.hp > p.hp) { flash[u.id] = "heal"; floats.push({ key: u.id + "-" + next.turn.n + "-" + Math.random().toString(36).slice(2, 6), unitId: u.id, kind: "heal", text: "+" + (u.hp - p.hp) }); }
    });
    prevStateRef.current = next;
    // Surface the single most important brand-new log line as a toast so players
    // notice big moments (a kill, a stun, an ability) without reading the feed.
    const prevLen = (prev.log || []).length, nextLog = next.log || [];
    if (nextLog.length > prevLen) {
      const fresh = nextLog.slice(0, nextLog.length - prevLen);   // log is newest-first (unshift)
      const rank = { kill: 5, debuff: 4, buff: 3, heal: 2, aura: 1, hit: 0, move: -1 };
      const top = fresh.slice().sort((a, b) => (rank[b.kind] ?? 0) - (rank[a.kind] ?? 0))[0];
      if (top && (rank[top.kind] ?? 0) >= 3) {
        const danger = top.kind === "kill" || top.kind === "debuff";
        tbToast(top.text, { kind: danger ? "danger" : undefined, icon: danger ? "Swords" : "Sparkles" });
      }
    }
    if (Object.keys(flash).length) {
      setFxFlash((f) => Object.assign({}, f, flash));
      setFxFloats((arr) => arr.concat(floats));
      const flashIds = Object.keys(flash);
      setTimeout(() => setFxFlash((f) => { const n = Object.assign({}, f); flashIds.forEach((id) => { if (n[id] === flash[id]) delete n[id]; }); return n; }), 520);
      const floatKeys = floats.map((f) => f.key);
      setTimeout(() => setFxFloats((arr) => arr.filter((f) => !floatKeys.includes(f.key))), 1000);
    }
  }

  React.useEffect(() => {
    const off = session.onChange((e) => {
      if (e.type === "error") tbToast(e.error, { kind: "danger" });
      if (e.type === "role") setMyRole(e.role);
      if (e.type === "status") setConn(e.status);
      if (e.type === "lobby") setLobby({ a: e.lobby.a, b: e.lobby.b });
      if (e.type === "thinking") setThinking(e.on);
      if (e.type === "state") {
        setSel(null); setAbilityMode(null);
        diffFx(prevStateRef.current, e.state);
        if (e.state && e.state.phase === "deploy") { rewardedRef.current = false; setReward(null); }
      }
      force();
    });
    return off;
  }, [session]);

  // Victory → progression: record result and unlock a card (the side YOU control).
  React.useEffect(() => {
    const st = session.getState();
    if (!st || !st.winner || rewardedRef.current) return;
    rewardedRef.current = true;
    const mySide = session.kind === "online" ? myRole : (session.mode === "ai" ? "A" : null);
    if (!mySide || !window.TBProfile || !window.TBProfile.isStarted()) return;  // hot-seat = practice, no rewards
    if (st.winner === mySide) {
      window.TBProfile.recordWin();
      const card = window.TBProfile.grantUnlock();
      if (card) setReward(card);
      else tbToast("Victory! You've collected every arcanum.", { title: "Complete" });
    } else {
      window.TBProfile.recordLoss();
    }
  });

  const state = session.getState();
  if (!state) {
    return <OnlineWaiting session={session} conn={conn} role={myRole} lobby={lobby} onExit={onExit} />;
  }

  // ----- DEPLOY phase -----
  if (state.phase === "deploy") {
    const myside = session.kind === "online" ? myRole : state.turn.side;
    const iCanDeploy = (myside === "A" || myside === "B") && !state.ready[myside];
    const bench = state.units.filter((u) => u.owner === myside && !u.placed);
    const free = iCanDeploy ? E.deployCells(state, myside) : {};
    const oppSide = myside === "A" ? "B" : "A";
    const placeUnit = (r, c) => { if (benchSel && free[r + "-" + c]) { session.send({ type: "PLACE", unitId: benchSel, to: { r, c } }); setBenchSel(null); } };

    return (
      <div className="board-screen">
        <div className="board-wrap">
          <div className="turn-banner">
            <span className="tag">Deploy</span>
            <span className="who" style={{ color: myside === "A" ? "var(--volt-green)" : "var(--blood-red)" }}>{state.players[myside]}</span>
            <span className="tag" style={{ color: "var(--fg-3)" }}>{bench.length ? `place ${bench.length} more` : "ready to fight"}</span>
          </div>

          <div className="board" style={{ gridTemplateColumns: `repeat(${state.cols},1fr)` }}>
            {state.terrain.map((rowArr, r) => rowArr.map((terr, c) => {
              const occ = E.unitAt(state, r, c);
              const mine = occ && occ.owner === myside;     // hide opponent placements pre-battle
              const cls = ["cell", "t-" + terr];
              if (free[r + "-" + c]) cls.push("deploy");
              return (
                <div key={r + "-" + c} className={cls.join(" ")} onClick={() => (occ && mine ? session.send({ type: "UNPLACE", unitId: occ.id }) : placeUnit(r, c))}>
                  {mine && <BoardToken state={state} unit={occ} selected={false} onClick={(e) => { e.stopPropagation(); session.send({ type: "UNPLACE", unitId: occ.id }); }} />}
                </div>
              );
            }))}
          </div>

          <div className="actions">
            <Btn kind="ghost" icon="Shuffle" onClick={() => session.send({ type: "AUTO" })} disabled={!iCanDeploy || !bench.length}>Auto-place</Btn>
            <Btn kind="primary" icon="Check" onClick={() => session.send({ type: "READY" })} disabled={!iCanDeploy || bench.length > 0}>Ready</Btn>
            <Btn kind="ghost" icon="ArrowLeft" onClick={onExit}>Leave</Btn>
          </div>

          <div className="log">
            <span className="e">Click a unit from your bench, then a glowing cell in your zone to place it. Click a placed unit to pick it back up.</span>
          </div>
        </div>

        <div className="board-side">
          <div className="inspect">
            <div style={{ fontSize: 11, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 10 }}>Your bench · {bench.length}</div>
            {bench.length ? (
              <div className="bench">
                {bench.map((u) => (
                  <div key={u.id} className={"bench-card" + (benchSel === u.id ? " sel" : "")} onClick={() => setBenchSel(u.id === benchSel ? null : u.id)} title={u.name}>
                    <TarotCard unit={window.TB_UNITS.find((x) => x.id === u.cardId) || u} flippable={false} flipped={false} />
                  </div>
                ))}
              </div>
            ) : <div className="empty-note">All units placed. Hit <b>Ready</b> when your formation looks good.</div>}
          </div>
          <div className="inspect" style={{ minHeight: 0 }}>
            <div style={{ fontSize: 11, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 8 }}>Status</div>
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13 }}>
              <span style={{ color: "var(--volt-green)" }}>{state.players.A} · {state.ready.A ? "ready" : "deploying"}</span>
              <span style={{ color: "var(--blood-red)" }}>{state.players.B} · {state.ready.B ? "ready" : "deploying"}</span>
            </div>
          </div>
        </div>
      </div>
    );
  }

  const myTurn = session.canControl(state.turn.side) && !state.winner;
  const selUnit = sel ? E.unitById(state, sel) : null;
  const abilities = selUnit ? E.activeAbilities(selUnit) : [];
  const ability = abilities.find((a) => a.id === abilityMode) || null; // the one currently in targeting mode

  // highlight maps for the selected unit
  let reach = {}, threat = {}, abilTargets = {};
  if (selUnit && myTurn && selUnit.owner === state.turn.side) {
    if (abilityMode && ability) abilTargets = E.abilityTargets(state, selUnit, ability);
    else {
      if (!selUnit.movedThisTurn && state.turn.ap > 0) reach = E.reachable(state, selUnit);
      if (!selUnit.actedThisTurn && state.turn.ap > 0) threat = E.attackable(state, selUnit);
    }
  }

  function clickUnit(u) {
    if (u.owner === state.turn.side && myTurn) { setAbilityMode(null); setSel(u.id === sel ? null : u.id); }
    else if (selUnit && threat[u.r + "-" + u.c]) { session.send({ type: "ATTACK", unitId: selUnit.id, targetId: u.id }); }
    else if (selUnit && ability && abilTargets[u.r + "-" + u.c]) { session.send({ type: "ABILITY", unitId: selUnit.id, abilityId: ability.id, targetId: u.id }); setAbilityMode(null); }
    else { setSel(null); setZoom(u); }   // not actionable → inspect it full-size
  }
  function clickCell(r, c) {
    const occ = E.unitAt(state, r, c);
    if (occ) return clickUnit(occ);
    if (selUnit && ability && ability.target === "token" && abilTargets[r + "-" + c]) {
      const tk = E.tokensAt(state, r, c).find((t) => t.type === ability.tokenType);
      if (tk) { session.send({ type: "ABILITY", unitId: selUnit.id, abilityId: ability.id, targetToken: tk.id }); setAbilityMode(null); return; }
    }
    if (selUnit && reach[r + "-" + c]) session.send({ type: "MOVE", unitId: selUnit.id, to: { r, c } });
    else setSel(null);
  }

  const sideName = (s) => state.players[s] || ("Player " + s);
  const apMax = Math.max(state.turn.ap, E.BASE_AP);
  const TOK = (window.TB_GLOSSARY || {}).TOKENS || {};

  return (
    <div className="board-screen">
      <div className="board-wrap">
        <div className="turn-banner">
          {state.winner ? (
            <React.Fragment><span className="tag">Result</span>
              <span className="who" style={{ color: "var(--volt-green)" }}>{sideName(state.winner)} wins</span></React.Fragment>
          ) : (
            <React.Fragment>
              <span className="tag">Turn {state.turn.n}</span>
              <span className="who" style={{ color: state.turn.side === "A" ? "var(--volt-green)" : "var(--blood-red)" }}>{sideName(state.turn.side)}</span>
              <span className="ap-bar"><span className="lbl">AP</span><span className="ap-pips">{Array.from({ length: apMax }).map((_, i) => <span key={i} className={"ap-pip" + (i < state.turn.ap ? " on" : "")} />)}</span></span>
              {state.fate && <span className="ap-bar fate-bar"><span className="lbl">Fate</span><span className="ap-pips">{Array.from({ length: state.fate.max }).map((_, i) => <span key={i} className={"ap-pip fate" + (i < state.fate.pool ? " on" : "")} />)}</span></span>}
              {!myTurn && <span className="tag" style={{ color: "var(--fg-3)" }}>{session.kind === "ai" || session.mode === "ai" ? (thinking ? "🧠 AI is thinking…" : "AI is moving…") : "opponent's move"}</span>}
            </React.Fragment>
          )}
        </div>

        <div className="board" style={{ gridTemplateColumns: `repeat(${state.cols},1fr)` }}>
          {state.terrain.map((rowArr, r) => rowArr.map((terr, c) => {
            const occ = E.unitAt(state, r, c);
            const cls = ["cell", "t-" + terr];
            if (reach[r + "-" + c]) cls.push("reach");
            if (threat[r + "-" + c]) cls.push("threat");
            if (abilTargets[r + "-" + c]) cls.push("abil");
            const cellToks = (state.tokens || []).filter((t) => t.r === r && t.c === c);
            return (
              <div key={r + "-" + c} className={cls.join(" ")} onClick={() => clickCell(r, c)}>
                {cellToks.length > 0 && (
                  <span className="cell-tokens">
                    {cellToks.map((t) => { const m = TOK[t.type] || { glyph: "•", tone: "neutral", label: t.type }; return <i key={t.id} className={"cell-token " + m.tone} title={m.label}>{m.glyph}</i>; })}
                  </span>
                )}
                {occ && <BoardToken state={state} unit={occ} selected={occ.id === sel}
                          flash={fxFlash[occ.id]} floats={fxFloats.filter((f) => f.unitId === occ.id)}
                          onClick={(e) => { e.stopPropagation(); clickUnit(occ); }} />}
              </div>
            );
          }))}
        </div>

        <div className="actions">
          {state.winner
            ? <React.Fragment>
                {session.reset && <Btn kind="primary" icon="RotateCcw" onClick={() => session.reset()}>Rematch</Btn>}
                <Btn kind="ghost" icon="ArrowLeft" onClick={onExit}>Back to Lobby</Btn>
              </React.Fragment>
            : <React.Fragment>
                {selUnit && !selUnit.actedThisTurn && myTurn && abilities.map((ab) => {
                  const afford = state.turn.ap >= ab.cost && (!ab.fate || (state.fate && state.fate.pool >= ab.fate));
                  const usedUp = ab.oncePerGame && (selUnit.usedOnce || []).includes(ab.id);
                  if (!afford || usedUp) return null;
                  const on = abilityMode === ab.id;
                  const costLabel = ab.fate ? ab.fate + " Fate" : ab.cost + " AP";
                  return <Btn key={ab.id} kind={on ? "primary" : "ghost"} icon="Sparkles" onClick={() => setAbilityMode(on ? null : ab.id)}>{on ? "Cancel" : ab.label + " (" + costLabel + ")"}</Btn>;
                })}
                <Btn kind="primary" icon="FastForward" onClick={() => session.send({ type: "END_TURN" })} disabled={!myTurn}>End Turn</Btn>
                <Btn kind="ghost" icon="Flag" onClick={onExit}>Forfeit</Btn>
              </React.Fragment>}
        </div>

        <div className="log feed">
          {state.log.slice(0, 6).map((e, i) => {
            const good = e.kind === "heal" || e.kind === "buff" || e.kind === "aura";
            const bad = e.kind === "hit" || e.kind === "kill" || e.kind === "debuff";
            const icon = e.kind === "kill" ? "Skull" : e.kind === "hit" ? "Swords" : e.kind === "heal" ? "Heart"
              : e.kind === "buff" ? "ArrowUp" : e.kind === "debuff" ? "ArrowDown" : e.kind === "move" ? "Footprints" : "Sparkles";
            return (
              <div key={i} className={"feed-row" + (i === 0 ? " latest" : "")}>
                <span className={"fi " + (good ? "good" : bad ? "bad" : "neutral")}><Icon name={icon} size={13} /></span>
                <span className="ft">{e.text}</span>
              </div>
            );
          })}
          {!state.log.length && <div className="feed-row"><span className="fi neutral"><Icon name="Sparkles" size={13} /></span><span className="ft">The board is set. Make your move.</span></div>}
        </div>
      </div>

      <div className="board-side">
        <div className="inspect">
          {selUnit ? (() => {
            const eff = E.effective(state, selUnit);
            return (
              <React.Fragment>
                <div className="nm">{selUnit.name}</div>
                <div className="sub" style={{ margin: "8px 0 12px" }}><span className="type-tag">{selUnit.type}</span><RoleTag role={selUnit.role} /></div>
                <MadChannels m={eff.mv} a={eff.atk} d={eff.def} />
                <div style={{ marginTop: 12, fontSize: 11, letterSpacing: ".14em", textTransform: "uppercase", color: "var(--fg-3)" }}>Health · {selUnit.hp}/{selUnit.maxHp}</div>
                <HealthPips value={selUnit.hp} max={selUnit.maxHp} />
                {(() => {
                  const conds = (selUnit.statuses || []).filter((s) => s.condition);
                  if (!conds.length) return null;
                  const ST = (window.TB_GLOSSARY || {}).STATUS || {};
                  return (
                    <div className="cond-strip">
                      {conds.map((s, i) => {
                        const m = ST[s.k] || { label: s.k, icon: "Circle", tone: "neutral" };
                        return <span key={i} className={"cond-pip " + m.tone} title={m.desc || ""}><Icon name={m.icon} size={11} />{m.label}{s.permanent ? "" : s.turns ? ` ·${s.turns}` : ""}</span>;
                      })}
                    </div>
                  );
                })()}
                {(() => {
                  const sup = selUnit.supply || 0, debt = selUnit.debt || 0, edge = selUnit.edge || 0;
                  const cat = window.TB_UNITS.find((u) => u.id === selUnit.cardId);
                  const supLabel = cat && /Star|Light|Harvest/.test(cat.mechanic || "") ? cat.mechanic : null;
                  const isSwords = selUnit.faction === "swords";
                  const carriesTide = state.tide && state.tide[selUnit.owner] === selUnit.id;
                  const isPent = selUnit.faction === "pentacles";
                  const coins = state.coins ? (state.coins[selUnit.owner] || 0) : 0;
                  const partner = cat && cat.resonance && cat.resonance.with;
                  const partnerCard = partner && window.TB_UNITS.find((u) => u.name === partner);
                  const resoLive = partnerCard && state.units.some((u) => u.alive && u.placed && u.owner === selUnit.owner && u.id !== selUnit.id && u.cardId === partnerCard.id);
                  if (!sup && !supLabel && !debt && !isSwords && !carriesTide && !isPent && !resoLive) return null;
                  return (
                    <div className="meter-strip">
                      {supLabel && <span className="meter-pip good" title={supLabel}><Icon name="Sparkles" size={11} />{supLabel} · {sup}</span>}
                      {debt > 0 && <span className="meter-pip bad" title="Death collapses at 6 Debt"><Icon name="Hourglass" size={11} />Debt {debt}/6</span>}
                      {isSwords && <span className={"meter-pip " + (edge >= 3 ? "good" : "neutral")} title="At Edge 3 the next attack is a guaranteed critical"><Icon name="Swords" size={11} />Edge {edge}/3</span>}
                      {carriesTide && <span className="meter-pip good" title="Carries the Tide — +1 all stats"><Icon name="Droplet" size={11} />The Tide</span>}
                      {isPent && <span className="meter-pip good" title="Shared Pentacles Coin pool"><Icon name="Coins" size={11} />Coins · {coins}</span>}
                      {resoLive && <span className="meter-pip good" title={cat.resonance.effect}><Icon name="Link2" size={11} />Resonance · {partner}</span>}
                    </div>
                  );
                })()}
                {abilities.map((ab) => {
                  const cat = window.TB_UNITS.find((u) => u.id === selUnit.cardId);
                  const text = cat && (cat.abilities.find((a) => a.k === ab.id) || {}).t;
                  return (
                    <div className={"abil-card" + (abilityMode === ab.id ? " sel" : "")} key={ab.id}>
                      <div className="ah"><b>{ab.label}</b><span className="cost">{ab.fate ? ab.fate + " Fate" : ab.cost + " AP"}{ab.oncePerGame ? " · once/game" : ""}</span></div>
                      <div className="at">{text || `Target a ${ab.target} within ${ab.range}.`}</div>
                    </div>
                  );
                })}
                <Btn kind="ghost" icon="Maximize2" onClick={() => setZoom(selUnit)}>View card</Btn>
              </React.Fragment>
            );
          })() : (
            <div className="empty-note">{myTurn ? "Select one of your units to see its range, stats and abilities. Green = move, red = strike, gold = ability target." : "Waiting for the other side."}</div>
          )}
        </div>
        <div className="inspect" style={{ minHeight: 0 }}>
          <div style={{ fontSize: 11, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: 8 }}>Forces{session.kind === "online" ? " · you are " + myRole : ""}</div>
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13 }}>
            <span style={{ color: "var(--volt-green)" }}>{sideName("A")} · {state.units.filter((u) => u.alive && u.owner === "A").length}</span>
            <span style={{ color: "var(--blood-red)" }}>{sideName("B")} · {state.units.filter((u) => u.alive && u.owner === "B").length}</span>
          </div>
        </div>
      </div>
      {reward && <RewardModal card={reward} wins={(window.TBProfile && window.TBProfile.get().wins) || 0} onClose={() => setReward(null)} />}
      {zoom && <CardZoom unit={zoom} state={state} onClose={() => setZoom(null)} />}
    </div>
  );
}

/* ---------- Online waiting room ---------- */
function OnlineWaiting({ session, conn, role, lobby, onExit }) {
  const code = session.room;
  const both = lobby.a && lobby.b;
  const origin = window.TBNet.defaultServerUrl();
  const joinLink = origin ? origin + "/?room=" + encodeURIComponent(code) : "";
  const copy = () => { try { navigator.clipboard.writeText(code); tbToast("Code copied.", { title: "Copied", icon: "Copy" }); } catch (e) {} };
  async function share() {
    const text = `Join my Tarot Battle — room ${code}`;
    try {
      if (navigator.share) { await navigator.share(joinLink ? { title: "Tarot Battle", text, url: joinLink } : { title: "Tarot Battle", text }); return; }
    } catch (e) { if (e && e.name === "AbortError") return; }
    try { await navigator.clipboard.writeText(joinLink || code); tbToast(joinLink ? "Invite link copied." : "Code copied.", { title: "Copied", icon: "Copy" }); }
    catch (e) { tbToast(joinLink || code, { title: "Share this", icon: "Share2", duration: 5000 }); }
  }
  const statusText = conn === "open" ? (both ? "Both players ready — dealing…" : "Waiting for an opponent to join…")
    : conn === "connecting" ? "Connecting to the server…"
    : conn === "reconnecting" ? "Connection dropped — reconnecting…"
    : conn === "error" ? "Couldn't reach the server. Check the URL and that the Worker is deployed."
    : "Connection closed.";
  const bad = conn === "error" || conn === "closed";
  return (
    <div className="lobby">
      <div className="screen-head"><div className="eyebrow">Online match</div><h1>Room {code}</h1></div>
      <div className="waiting">
        <div className="wait-code" onClick={copy} title="Copy code">
          <span className="code-chip">{code}</span>
          <Icon name="Copy" size={16} />
        </div>
        <div style={{ textAlign: "center", marginBottom: 6 }}>
          <Btn kind="primary" icon="Share2" onClick={share}>Share invite</Btn>
        </div>
        <div className="seats">
          <div className={"seat" + (lobby.a ? " on" : "") + (role === "A" ? " me" : "")}>
            <span className="dotA" /><b>{lobby.a || "Open seat"}</b><span className="srole">A{role === "A" ? " · you" : ""}</span>
          </div>
          <div className="vsmini">VS</div>
          <div className={"seat" + (lobby.b ? " on" : "") + (role === "B" ? " me" : "")}>
            <span className="dotB" /><b>{lobby.b || "Waiting…"}</b><span className="srole">B{role === "B" ? " · you" : ""}</span>
          </div>
        </div>
        <div className={"wait-status" + (bad ? " bad" : "") + (conn === "reconnecting" ? " warn" : "")}>
          {conn === "connecting" && <Icon name="Loader" size={15} />}
          {conn === "reconnecting" && <Icon name="RefreshCw" size={15} />}
          {conn === "open" && !both && <Icon name="Hourglass" size={15} />}
          {bad && <Icon name="TriangleAlert" size={15} />}
          <span>{statusText}</span>
        </div>
        <p className="conn-note" style={{ textAlign: "center" }}>Tap <b>Share invite</b> to send a one-tap link, or give a friend the code <b>{code}</b>. They open Tarot Battle, choose <b>Board → Online</b>, and join as side B.</p>
        <div style={{ textAlign: "center" }}><Btn kind="ghost" icon="ArrowLeft" onClick={onExit}>Leave room</Btn></div>
      </div>
    </div>
  );
}

/* ---------- Hub: lobby ↔ board ---------- */
function BattleHub() {
  const [session, setSession] = React.useState(null);

  function start(opts) {
    const myCards = cardsFor(opts.squad && opts.squad.length ? opts.squad : SQUAD_A);
    const mySquad = window.TBNet.squadFromCards(myCards);
    if (opts.mode === "online") {
      if (!opts.serverUrl) { tbToast("Enter your Worker URL to play online.", { kind: "danger" }); return; }
      const s = window.TBNet.createRemoteSession({ url: opts.serverUrl, room: opts.room, name: opts.name, create: opts.create, squad: mySquad });
      setSession(s);
      tbToast("Room " + opts.room + " — share this code.", { title: "Online", icon: "Globe", duration: 4000 });
    } else {
      // local: your chosen squad is side A; the opponent fields a preset
      const squads = { A: mySquad, B: window.TBNet.squadFromCards(cardsFor(SQUAD_B)) };
      setSession(window.TBNet.createLocalSession({ squads, mode: opts.mode, brain: opts.brain }));
    }
  }
  function exit() { if (session && session.close) session.close(); setSession(null); }

  return session
    ? <BoardView session={session} onExit={exit} />
    : <BattleLobby onStart={start} />;
}

Object.assign(window, { BattleHub, BattleLobby, BoardView, OnlineWaiting, SquadEditor });
