import { ABILITY_ORDER } from "../game/data";
import { HEALER_CLASSES, resolveSlottedAbility } from "../game/healers";
import { BOSS_DEFINITIONS } from "../game/bossCatalog";
import { BARRIER_RADIUS, GLOBAL_COOLDOWN_SECONDS, abilityRemaining, barrierProtects, healerFieldContains, upcomingEncounterMechanic, useGameStore } from "../game/store";
import { runAbilityCastTime, runAbilityCooldown, runAbilityManaCost } from "../game/roguelike";
import { PARTY_ABILITY_NAMES, tankAuraProtects } from "../game/partyCombat";
import type { BottomTab, PartyMember } from "../game/types";
import { useActiveHunter, useFrontendStore } from "../frontend/store";
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs";
import {
  HOCKEY_ARENA_CENTER_Z,
  HOCKEY_GOAL_HALF_WIDTH,
  HOCKEY_HEALER_GOAL_Z,
  HOCKEY_NPC_PADDLE_HALF_WIDTH,
  HOCKEY_NPC_PADDLE_Z,
  HOCKEY_NPC_GOAL_Z,
} from "../game/hockeyHealing";
import {
  HOCKEY_PVP_GOAL_DAMAGE,
  HOCKEY_PVP_GOAL_HALF_WIDTH,
  HOCKEY_PVP_GOAL_Z,
  HOCKEY_PVP_SIDE_OFFSET_Z,
} from "../game/hockeyHealingPvp";
import { bottomTabsFor } from "../game/bottomTabs";
import {
  BLOCKBREAKER_BREACH_DAMAGE,
  BLOCKBREAKER_BRICK_COLORS,
  BLOCKBREAKER_DANGER_Z,
  blockbreakerColumnX,
  blockbreakerRowZ,
  blockbreakerTimeMultiplier,
} from "../game/blockbreaker";
import { aetherShipColor } from "./aetherAssaultVisuals";
import { ABILITY_CONTROLLER_BINDINGS } from "../game/controllerBindings";
import { RpgRunTacticalPanel } from "./rpgRoguelike/RpgRunTacticalPanel";
import { isBeaconOfLightTarget } from "../game/healerMechanics";

function RewardSummary() {
  const rewards = useFrontendStore((state) => state.recentRewards);
  if (!rewards.length) return null;
  return <div className="reward-summary" aria-label="Boss rewards">{rewards.map((reward, index) => <span key={`${reward.drop.id}-${index}`}><b>{reward.drop.glyph}</b>{reward.drop.name} ×{reward.quantity}{reward.pet ? <i> + {reward.pet.name}</i> : null}</span>)}</div>;
}

function HealthBar({ member }: { member: PartyMember }) {
  const health = Math.max(0, (member.hp / member.maxHp) * 100);
  const shield = Math.min(38, (member.absorb / member.maxHp) * 100);
  const shieldLeft = Math.min(health, 100 - shield);
  return (
    <span className="health-bar">
      <i className={health < 35 ? "is-low" : ""} style={{ width: `${health}%` }} />
      {shield > 0 && <b style={{ left: `${shieldLeft}%`, width: `${shield}%` }} />}
    </span>
  );
}

function PartyFrame({ member }: { member: PartyMember }) {
  const selected = useGameStore((state) => state.selectedMemberId === member.id);
  const selectMember = useGameStore((state) => state.selectMember);
  const time = useGameStore((state) => state.time);
  const healerMechanic = useGameStore((state) => state.healerMechanic);
  const beaconed = isBeaconOfLightTarget(member.id, healerMechanic, time);
  const activeHealingEffects = member.healingEffects.filter((effect) => effect.expiresAt > time).slice(0, 3);
  const knockedRemaining = Math.max(0, member.knockedUntil - time);
  const barrier = useGameStore((state) => state.barrier);
  const tankAura = useGameStore((state) => state.partyCombat.tankAura);
  const tankPosition = useGameStore((state) => state.partyPositions[state.partyCombat.tankAura.sourceId]);
  const combatant = useGameStore((state) => member.id === "aelia" ? undefined : state.partyCombat.combatants[member.id]);
  const position = useGameStore((state) => state.partyPositions[member.id]);
  const protectedByBarrier = barrierProtects(position, barrier, time);
  const linkedBySpirit = barrier.kind === "spirit-link" && healerFieldContains(position, barrier, time);
  const protectedByTank = tankAuraProtects(position, tankPosition, tankAura, time);
  const currentAction = combatant?.visualAction && combatant.visualAction.endsAt > time
    ? PARTY_ABILITY_NAMES[combatant.visualAction.abilityId]
    : null;
  return (
    <button
      className={`party-frame ${selected ? "is-selected" : ""} ${beaconed ? "is-beacon" : ""} ${member.hp <= 0 ? "is-down" : ""}`}
      onClick={() => selectMember(member.id)}
      aria-pressed={selected}
      aria-label={`${member.name}, ${Math.ceil(member.hp)} health${beaconed ? ", Beacon of Light" : ""}`}
    >
      <span className="party-avatar" style={{ "--member-color": member.color } as React.CSSProperties}>{member.name[0]}</span>
      <span className="party-data">
        <span className="party-name"><strong>{member.name}</strong><em>{Math.ceil(member.hp)} / {member.maxHp}</em></span>
        <HealthBar member={member} />
        <small>{currentAction ?? member.className}</small>
      </span>
      <span className="effect-stack">
        {beaconed && <i className="effect beacon-effect" title={`Beacon of Light: ${Math.max(0, healerMechanic.beaconExpiresAt - time).toFixed(1)} seconds`}>✦</i>}
        {member.absorb > 0 && <i className="effect shield-effect" title={`${Math.ceil(member.absorb)} absorption`}>◇</i>}
        {activeHealingEffects.map((effect) => {
          const label = effect.id === "renew" ? "R" : effect.id === "regrowth" ? "G" : effect.id === "rejuvenation" ? "J" : effect.id === "lifebloom" ? `L${effect.stacks}` : effect.id === "wild-growth" ? "W" : "T";
          return <i key={effect.id} className="effect renew-effect" title={`${effect.id}: ${Math.max(0, effect.expiresAt - time).toFixed(1)} seconds`}>{label}</i>;
        })}
        {member.reactiveHeal && member.reactiveHeal.expiresAt > time && <i className="effect earth-shield-effect" title={`Earth Shield: ${member.reactiveHeal.charges} charges`}>{member.reactiveHeal.charges}</i>}
        {member.debuffs.length > 0 && <i className="effect debuff-effect" title={`${member.debuffs[0].name} — Purify`}>!</i>}
        {protectedByBarrier && <i className="effect barrier-effect" title="Barrier: 30% reduced damage">B</i>}
        {linkedBySpirit && <i className="effect spirit-link-effect" title="Spirit Link: health equalized each second">S</i>}
        {protectedByTank && <i className="effect tank-aura-effect" title="Bulwark March: 30% reduced damage">T</i>}
        {knockedRemaining > 0 && <i className="effect knock-effect" title={`Knocked down: ${knockedRemaining.toFixed(1)} seconds`}>KD</i>}
      </span>
    </button>
  );
}

function PartyList() {
  const party = useGameStore((state) => state.party);
  return (
    <div className="party-list">
      <div className="section-label"><span>Formation</span><small>Q / E target</small></div>
      {party.map((member) => <PartyFrame key={member.id} member={member} />)}
    </div>
  );
}

function AbilityButton({ abilityId }: { abilityId: (typeof ABILITY_ORDER)[number] }) {
  const healerClassId = useGameStore((state) => state.healerClassId);
  const ability = useGameStore((state) => resolveSlottedAbility(state.abilityLoadout, abilityId));
  const time = useGameStore((state) => state.time);
  const cooldowns = useGameStore((state) => state.cooldowns);
  const globalCooldownUntil = useGameStore((state) => state.globalCooldownUntil);
  const mana = useGameStore((state) => state.mana);
  const phase = useGameStore((state) => state.phase);
  const healerAlive = useGameStore((state) => state.party.some((member) => member.id === "aelia" && member.hp > 0));
  const selected = useGameStore((state) => state.party.find((member) => member.id === state.selectedMemberId)!);
  const activeCast = useGameStore((state) => state.activeCast);
  const castAbility = useGameStore((state) => state.castAbility);
  const runModifiers = useGameStore((state) => state.runModifiers);
  const healerMechanic = useGameStore((state) => state.healerMechanic);
  if (!ability) {
    return <button className={`ability ability-${abilityId} is-empty`} disabled aria-label={`Empty ${abilityId}`}><span className="ability-key">{Number(abilityId.slice(-1))}</span><span className="ability-icon">—</span><span className="ability-copy"><strong>Empty</strong><small>No spell drafted</small></span><span className="ability-pad">{ABILITY_CONTROLLER_BINDINGS[abilityId].glyph}</span></button>;
  }
  const remaining = abilityRemaining(abilityId, time, cooldowns);
  const manaCost = runAbilityManaCost(abilityId, ability.mana, runModifiers);
  const baseCastTime = ability.castTime ? runAbilityCastTime(abilityId, ability.castTime, runModifiers) : 0;
  const castTime = ability.id === "shaman-healing-wave" && healerMechanic.resource > 0 ? baseCastTime * 0.5 : baseCastTime;
  const cooldownDuration = runAbilityCooldown(abilityId, ability.cooldown, runModifiers);
  const globalRemaining = Math.max(0, globalCooldownUntil - time);
  const noDispel = ability.pulseKind === "cleanse" && selected.debuffs.length === 0;
  const invalidTarget = ability.targeting === "ally" && selected.hp <= 0;
  const disabled = phase !== "combat" || !healerAlive || activeCast !== null || remaining > 0 || globalRemaining > 0 || mana < manaCost || noDispel || invalidTarget;
  const resourceName = HEALER_CLASSES[healerClassId].resourceName.toLowerCase();
  const resourceCopy = `${manaCost ? `${manaCost} ${resourceName}` : "free"}${castTime ? ` · ${castTime.toFixed(1)}s` : ""}`;

  return (
    <button
      className={`ability ability-${abilityId} ${remaining > 0 || globalRemaining > 0 ? "on-cooldown" : ""}`}
      style={{ "--ability-color": ability.color } as React.CSSProperties}
      onClick={() => castAbility(abilityId)}
      disabled={disabled}
      title={ability.description}
      aria-label={`${ability.name}. ${ability.description}`}
    >
      <span className="ability-key">{Number(abilityId.slice(-1))}</span>
      <span className="ability-icon">{ability.icon}</span>
      <span className="ability-copy"><strong>{ability.shortName}</strong><small>{resourceCopy}</small></span>
      <span className="ability-pad">{ABILITY_CONTROLLER_BINDINGS[abilityId].glyph}</span>
      {remaining > 0 && (
        <span className="cooldown-mask" style={{ "--cooldown-progress": Math.min(1, remaining / cooldownDuration) } as React.CSSProperties}>
          <b>{remaining < 1 ? remaining.toFixed(1) : Math.ceil(remaining)}</b>
        </span>
      )}
      {remaining <= 0 && globalRemaining > 0 && (
        <span className="cooldown-mask global-cooldown" style={{ "--cooldown-progress": Math.min(1, globalRemaining / GLOBAL_COOLDOWN_SECONDS) } as React.CSSProperties}>
          <b>{globalRemaining.toFixed(1)}</b>
        </span>
      )}
    </button>
  );
}

function AbilityTray() {
  const healerClassId = useGameStore((state) => state.healerClassId);
  const healer = HEALER_CLASSES[healerClassId];
  const mana = useGameStore((state) => state.mana);
  const maxMana = useGameStore((state) => state.maxMana);
  const healerMechanic = useGameStore((state) => state.healerMechanic);
  const selected = useGameStore((state) => state.party.find((member) => member.id === state.selectedMemberId)!);
  const time = useGameStore((state) => state.time);
  const boss = useGameStore((state) => state.boss);
  const bossMotion = useGameStore((state) => state.bossMotion);
  const additionalBosses = useGameStore((state) => state.additionalBosses);
  const mechanic = upcomingEncounterMechanic({ boss, bossMotion, additionalBosses, time });
  const activityMode = useGameStore((state) => state.activityMode);
  const hockeyReturns = useGameStore((state) => state.hockey.returns);
  const bossKills = useGameStore((state) => state.endlessBossKills);
  const hockeyPvp = useGameStore((state) => state.hockeyPvp);
  const blockbreaker = useGameStore((state) => state.blockbreaker);
  const aetherAssault = useGameStore((state) => state.aetherAssault);
  const hockeyMode = activityMode === "hockey-healing";
  const pvpMode = activityMode === "hockey-healing-pvp";
  const blockbreakerMode = activityMode === "blockbreaker";
  const aetherMode = activityMode === "aether-assault";
  const duration = `${Math.floor(time / 60)}:${String(Math.floor(time % 60)).padStart(2, "0")}`;
  const nextRowSeconds = Math.max(0, blockbreaker.nextRowAt - time);
  return (
    <div className="ability-column">
      <div className={`ability-meta ${hockeyMode || pvpMode || blockbreakerMode || aetherMode ? "is-hockey" : ""} ${pvpMode ? "is-pvp" : ""}`}>
        <div className="target-chip"><span>Target</span><strong>{selected.name}</strong></div>
        <div className="resource-stack">
          <div className="mana-wrap"><span>{healer.resourceName}</span><b>{Math.ceil(mana)}</b><i><em style={{ width: `${(mana / maxMana) * 100}%` }} /></i></div>
          {healer.secondaryResourceName && <div className="class-resource"><span>{healer.secondaryResourceName}</span><b>{healerMechanic.resource} / {healerMechanic.maxResource}</b></div>}
        </div>
        {hockeyMode && <div className="hockey-run-meta"><span>Returns</span><strong>{hockeyReturns}</strong><small>{duration} · {bossKills} KOs</small></div>}
        {pvpMode && <div className="hockey-run-meta pvp-run-meta"><span>Goals · Bosses</span><strong>{hockeyPvp.opponentGoalsConceded}–{hockeyPvp.localGoalsConceded}</strong><small>{bossKills}–{hockeyPvp.opponentBossKills} · {hockeyPvp.opponentName}</small></div>}
        {blockbreakerMode && <div className="hockey-run-meta blockbreaker-run-meta"><span>Score · Bricks</span><strong>{blockbreaker.score} · {blockbreaker.bricksBroken}</strong><small>{blockbreakerTimeMultiplier(time).toFixed(1)}× · next row {nextRowSeconds.toFixed(1)}s</small></div>}
        {aetherMode && <div className="hockey-run-meta aether-run-meta"><span>Score · Wave</span><strong>{aetherAssault.score} · {aetherAssault.wave}</strong><small>{aetherAssault.multiplier.toFixed(2)}× · {aetherAssault.ships.length} ships</small></div>}
      </div>
      <div className="ability-grid">
        {ABILITY_ORDER.map((abilityId) => <AbilityButton key={abilityId} abilityId={abilityId} />)}
      </div>
      <div className={`incoming-strip ${mechanic.urgent ? "is-urgent" : ""}`}>
        <span className="incoming-icon">⌁</span>
        <span><small>Incoming</small><strong>{mechanic.name}</strong></span>
        <b>{mechanic.remaining.toFixed(1)}s</b>
        <i><em style={{ width: `${Math.min(100, (mechanic.remaining / mechanic.cycle) * 100)}%` }} /></i>
      </div>
    </div>
  );
}

function BriefingPanel() {
  const healerClassId = useGameStore((state) => state.healerClassId);
  const healer = HEALER_CLASSES[healerClassId];
  const startEncounter = useGameStore((state) => state.startEncounter);
  const primaryBoss = useGameStore((state) => state.boss);
  const additionalBosses = useGameStore((state) => state.additionalBosses);
  const bosses = [primaryBoss, ...additionalBosses.map((entry) => entry.boss)];
  const definitions = bosses.map((boss) => BOSS_DEFINITIONS[boss.id]);
  const bossNames = bosses.map((boss) => boss.name).join(" & ");
  const activityMode = useGameStore((state) => state.activityMode);
  const hockeyMode = activityMode === "hockey-healing";
  const pvpMode = activityMode === "hockey-healing-pvp";
  const blockbreakerMode = activityMode === "blockbreaker";
  const aetherMode = activityMode === "aether-assault";
  const opponentName = useGameStore((state) => state.hockeyPvp.opponentName);
  return (
    <div className="briefing-panel">
      <div className="briefing-class">
        <div className="class-crest" style={{ color: healer.color }}>{healer.icon}</div>
        <span>Chosen discipline</span>
        <h2>{healer.specialization}</h2>
        <p>{hockeyMode ? "Defend the wide goal while healing through two bosses. Left-stick direction sets every puck return; the moving enemy paddle tracks it and strikes it back. Each fallen boss awards loot and rolls its pet chance before replacement." : pvpMode ? `Face ${opponentName}. Both parties use normalized base gear and fight the same boss order. Every boss kill adds 5% global healing Dampening. Aim returns with left stick. A goal deals ${HOCKEY_PVP_GOAL_DAMAGE} damage to every member of the conceding party. Boss kills still award loot and pet chances.` : blockbreakerMode ? `Aim the puck into advancing five-column color rows while healing through two bosses. Orthogonal matching clusters break together. Rows start every 10 seconds and accelerate. Misses safely re-serve; each breach deals ${BLOCKBREAKER_BREACH_DAMAGE} damage to every party member.` : aetherMode ? "Move freely through the full runway while your arcane focus fires automatically. Heal with your normal kit. Dodge enemy volleys and diving ships; ship hits only threaten the healer. Bosses and rewards continue independently." : <>{healer.description} {definitions.map((boss) => boss.briefing).join(" ")}</>}</p>
        <button className="start-button" onClick={startEncounter}><span>{hockeyMode ? "Begin Hockey Healing" : pvpMode ? `Face ${opponentName}` : blockbreakerMode ? "Begin Blockbreaker" : aetherMode ? "Begin Aether Assault" : `Face ${bossNames}`}</span><small>{DEFAULT_CONTROLLER_GLYPHS.start} / ENTER</small></button>
      </div>
      <div className="briefing-kit">
        <div className="section-label"><span>Prepared skills</span><small>6 equipped</small></div>
        <div className="kit-grid">
          {ABILITY_ORDER.map((abilityId) => {
            const ability = healer.abilities[abilityId];
            return (
              <div className="kit-skill" key={abilityId} style={{ "--ability-color": ability.color } as React.CSSProperties}>
                <b>{ability.icon}</b><span><strong>{ability.shortName}</strong><small>{ability.description}</small></span>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function EndPanel({ onExit }: { onExit?: () => void }) {
  const hunter = useActiveHunter();
  const phase = useGameStore((state) => state.phase);
  const runMode = useGameStore((state) => state.runMode);
  const activityMode = useGameStore((state) => state.activityMode);
  const round = useGameStore((state) => state.round);
  const endlessMode = useGameStore((state) => state.endlessMode);
  const endlessBossKills = useGameStore((state) => state.endlessBossKills);
  const endlessChoiceSelection = useGameStore((state) => state.endlessChoiceSelection);
  const setEndlessChoiceSelection = useGameStore((state) => state.setEndlessChoiceSelection);
  const startRogueTrialsEndless = useGameStore((state) => state.startRogueTrialsEndless);
  const time = useGameStore((state) => state.time);
  const party = useGameStore((state) => state.party);
  const restart = useGameStore((state) => state.restart);
  const startEncounter = useGameStore((state) => state.startEncounter);
  const totalHp = party.reduce((sum, member) => sum + member.hp, 0);
  const totalMax = party.reduce((sum, member) => sum + member.maxHp, 0);
  const hockey = useGameStore((state) => state.hockey);
  const hockeyPvp = useGameStore((state) => state.hockeyPvp);
  const blockbreaker = useGameStore((state) => state.blockbreaker);
  const aetherAssault = useGameStore((state) => state.aetherAssault);
  const showEndlessChoice = phase === "victory" && runMode === "rogue-trials" && round === 5 && !endlessMode;
  const hockeyDefeat = phase === "defeat" && activityMode === "hockey-healing";
  const blockbreakerDefeat = phase === "defeat" && activityMode === "blockbreaker";
  const aetherDefeat = phase === "defeat" && activityMode === "aether-assault";
  const pvpMatch = activityMode === "hockey-healing-pvp";
  const endlessDefeat = phase === "defeat" && endlessMode && !hockeyDefeat && !blockbreakerDefeat && !aetherDefeat;
  const endlessHighScore = hunter?.stats.highestRogueTrialsEndlessKills ?? 0;
  const endlessHighScoreLabel = `${endlessHighScore} ${endlessHighScore === 1 ? "boss" : "bosses"}`;
  return (
    <div className={`end-panel end-${phase}`}>
      <span className="end-mark">{phase === "victory" ? "✦" : "×"}</span>
      <small>{showEndlessChoice ? "ROGUE TRIALS CLEARED" : hockeyDefeat ? "HOCKEY HEALING COMPLETE" : blockbreakerDefeat ? "BLOCKBREAKER RUN COMPLETE" : aetherDefeat ? "AETHER ASSAULT COMPLETE" : pvpMatch ? phase === "victory" ? "PVP MATCH WON" : "PVP MATCH LOST" : endlessDefeat ? "ENDLESS RUN COMPLETE" : phase === "victory" ? "TRIAL COMPLETE" : "FORMATION LOST"}</small>
      <h2>{showEndlessChoice ? "The trial can continue" : hockeyDefeat ? `${hockey.returns} pucks returned` : blockbreakerDefeat ? `${blockbreaker.score} points scored` : aetherDefeat ? `${aetherAssault.score} points scored` : pvpMatch ? phase === "victory" ? `${hockeyPvp.opponentName} fell first` : `${hockeyPvp.opponentName} wins` : endlessDefeat ? `${endlessBossKills} bosses defeated` : phase === "victory" ? "Five souls endure" : "The vault claims its due"}</h2>
      <div className="result-stats">
        <span><small>Duration</small><strong>{Math.floor(time / 60)}:{String(Math.floor(time % 60)).padStart(2, "0")}</strong></span>
        <span><small>{hockeyDefeat ? "Puck returns" : blockbreakerDefeat ? "Bricks broken" : aetherDefeat ? "Wave reached" : pvpMatch ? "Goals" : "Party vitality"}</small><strong>{hockeyDefeat ? hockey.returns : blockbreakerDefeat ? blockbreaker.bricksBroken : aetherDefeat ? aetherAssault.wave : pvpMatch ? `${hockeyPvp.opponentGoalsConceded}–${hockeyPvp.localGoalsConceded}` : `${Math.round((totalHp / totalMax) * 100)}%`}</strong></span>
        <span><small>{hockeyDefeat || blockbreakerDefeat || aetherDefeat ? "Boss kills" : pvpMatch ? "Boss kills" : endlessDefeat ? "Endless kills" : "Boss"}</small><strong>{hockeyDefeat || blockbreakerDefeat || aetherDefeat || endlessDefeat ? endlessBossKills : pvpMatch ? `${endlessBossKills}–${hockeyPvp.opponentBossKills}` : phase === "victory" ? "Defeated" : "Standing"}</strong></span>
      </div>
      {(phase === "victory" || hockeyDefeat || blockbreakerDefeat || aetherDefeat) && <RewardSummary />}
      {showEndlessChoice ? <div className="end-actions endless-choice-actions">
        <button
          className={endlessChoiceSelection === "continue" ? "is-controller-selected" : ""}
          onPointerEnter={() => setEndlessChoiceSelection("continue")}
          onClick={startRogueTrialsEndless}
          aria-label={`Endless Mode. Current high score: ${endlessHighScoreLabel}.`}
        ><span>Endless Mode</span><small>High score · {endlessHighScoreLabel}</small></button>
        <button
          className={`secondary ${endlessChoiceSelection === "quit" ? "is-controller-selected" : ""}`}
          onPointerEnter={() => setEndlessChoiceSelection("quit")}
          onClick={onExit}
        >Quit to Main Menu</button>
      </div> : <div className="end-actions">
        <button onClick={() => { if (pvpMatch) onExit?.(); else { restart(); startEncounter(); } }}>{pvpMatch ? "Find new opponent" : "Run again"}</button>
        <button className="secondary" onClick={endlessDefeat || hockeyDefeat || blockbreakerDefeat || aetherDefeat || pvpMatch ? onExit : restart}>{endlessDefeat || hockeyDefeat || blockbreakerDefeat || aetherDefeat || pvpMatch ? "Return to main menu" : "Return to briefing"}</button>
      </div>}
    </div>
  );
}

function IntermissionStatusPanel() {
  const round = useGameStore((state) => state.round);
  return (
    <div className="intermission-status" aria-label={`Round ${round} cleared. Choose a blessing on the top display.`}>
      <i>✦</i>
      <span>Round {round} cleared</span>
      <h2>Choose on top display</h2>
      <p>Next encounter stays locked until one blessing is claimed.</p>
      <RewardSummary />
      <small>Use D-pad to choose · {DEFAULT_CONTROLLER_GLYPHS.confirm} to claim</small>
    </div>
  );
}

function CombatPanel({ onExit }: { onExit?: () => void }) {
  const phase = useGameStore((state) => state.phase);
  if (phase === "briefing") return <BriefingPanel />;
  if (phase === "intermission") return <IntermissionStatusPanel />;
  if (phase === "victory" || phase === "defeat") return <EndPanel onExit={onExit} />;
  return <div className="combat-panel"><PartyList /><AbilityTray /></div>;
}

function MapPanel() {
  const playerPosition = useGameStore((state) => state.playerPosition);
  const partyPositions = useGameStore((state) => state.partyPositions);
  const bossMotion = useGameStore((state) => state.bossMotion);
  const additionalBosses = useGameStore((state) => state.additionalBosses);
  const barrier = useGameStore((state) => state.barrier);
  const time = useGameStore((state) => state.time);
  const phase = useGameStore((state) => state.phase);
  const bossId = useGameStore((state) => state.bossId);
  const activityMode = useGameStore((state) => state.activityMode);
  const hockey = useGameStore((state) => state.hockey);
  const hockeyPvp = useGameStore((state) => state.hockeyPvp);
  const blockbreaker = useGameStore((state) => state.blockbreaker);
  const aetherAssault = useGameStore((state) => state.aetherAssault);
  const hockeyPvpOpponent = useGameStore((state) => state.hockeyPvpOpponent);
  const bossDefinition = BOSS_DEFINITIONS[bossId];
  const playerX = 120 + playerPosition[0] * 7;
  const playerY = 143 + playerPosition[1] * 5.3;
  const bossMotions = [bossMotion, ...additionalBosses.map((entry) => entry.motion)];
  if (activityMode === "aether-assault") {
    const mapX = (x: number) => 120 + x * 8.5;
    const mapY = (z: number) => 140 + (z - HOCKEY_ARENA_CENTER_Z) * 8;
    return (
      <div className="map-panel hockey-map-panel aether-map-panel">
        <div className="map-copy">
          <span>Aether Assault</span>
          <h2>Arcane Formation Runway</h2>
          <p>Focus fire stays automatic. Move anywhere in the rink, heal freely, and evade red volleys plus amber dive warnings.</p>
          <div className="map-legend"><i className="legend-party" /> Party <i className="legend-boss" /> Bosses <i className="legend-ship" /> Ships <i className="legend-shot" /> Shots</div>
        </div>
        <div className="map-canvas">
          <svg viewBox="0 0 240 280" role="img" aria-label="Aether Assault tactical map">
            <defs><linearGradient id="aether-room" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#18394a" /><stop offset="0.55" stopColor="#d9f3ef" /><stop offset="1" stopColor="#dff8f4" /></linearGradient></defs>
            <rect className="map-room hockey-map-room" x="35" y="20" width="170" height="240" rx="3" fill="url(#aether-room)" />
            <path className="map-ring aether-map-lanes" d="M63 20 V260 M91 20 V260 M120 20 V260 M149 20 V260 M177 20 V260" />
            {aetherAssault.ships.map((ship, index) => {
              const color = aetherShipColor(aetherAssault.seed, aetherAssault.wave, index, ship.kind);
              return <g key={ship.id}>
                {ship.phase === "diving" && <circle className="aether-map-warning" cx={mapX(ship.position[0])} cy={mapY(ship.position[1])} r="8" />}
                <path className={`aether-map-ship is-${ship.kind}`} style={{ fill: color, filter: `drop-shadow(0 0 4px ${color})` }} d={`M${mapX(ship.position[0])} ${mapY(ship.position[1]) - 5} l6 9 h-12 z`} />
              </g>;
            })}
            {aetherAssault.playerShots.map((shot) => <circle key={`player-shot-${shot.id}`} className="aether-map-player-shot" cx={mapX(shot.position[0])} cy={mapY(shot.position[1])} r="2" />)}
            {aetherAssault.enemyShots.map((shot) => <circle key={`enemy-shot-${shot.id}`} className="aether-map-enemy-shot" cx={mapX(shot.position[0])} cy={mapY(shot.position[1])} r="2.5" />)}
            {bossMotions.map((motion, index) => <circle key={`${motion.bossId}-${index}`} className="map-boss" cx={mapX(motion.position[0])} cy={mapY(motion.position[1])} r="7" />)}
            {(["brann", "nia", "orin", "vale"] as const).map((memberId) => <circle key={memberId} className={`map-ally map-ally-${memberId}`} cx={mapX(partyPositions[memberId][0])} cy={mapY(partyPositions[memberId][1])} r="4" />)}
            <circle className="map-player-pulse" cx={mapX(playerPosition[0])} cy={mapY(playerPosition[1])} r="11" />
            <circle className="map-player" cx={mapX(playerPosition[0])} cy={mapY(playerPosition[1])} r="6" />
            {barrier.expiresAt > time && <ellipse className="map-barrier" cx={mapX(barrier.center[0])} cy={mapY(barrier.center[1])} rx={BARRIER_RADIUS * 8.5} ry={BARRIER_RADIUS * 8} />}
          </svg>
          <span className="map-state">{phase === "combat" ? `WAVE ${aetherAssault.wave} · ${aetherAssault.score} SCORE` : "FORMATION PREVIEW"}</span>
        </div>
      </div>
    );
  }
  if (activityMode === "blockbreaker") {
    const mapX = (x: number) => 120 + x * 8.5;
    const mapY = (z: number) => 140 + (z - HOCKEY_ARENA_CENTER_Z) * 8;
    const colors: Record<(typeof BLOCKBREAKER_BRICK_COLORS)[number], string> = {
      cyan: "#36d9ef",
      amber: "#f1b74f",
      magenta: "#e85aa9",
      lime: "#93db54",
    };
    return (
      <div className="map-panel hockey-map-panel blockbreaker-map-panel">
        <div className="map-copy">
          <span>Blockbreaker</span>
          <h2>Advancing Color Wall</h2>
          <p>Match orthogonal colors. Crossing bricks disappear and deal {BLOCKBREAKER_BREACH_DAMAGE} partywide damage.</p>
          <div className="map-legend"><i className="legend-party" /> Party <i className="legend-boss" /> Bosses <i className="legend-brick" /> Bricks <i className="legend-exit" /> Puck</div>
        </div>
        <div className="map-canvas">
          <svg viewBox="0 0 240 280" role="img" aria-label="Blockbreaker tactical map">
            <defs><linearGradient id="blockbreaker-room" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#251717" /><stop offset="0.5" stopColor="#10262d" /><stop offset="1" stopColor="#092d39" /></linearGradient></defs>
            <rect className="map-room hockey-map-room" x="35" y="20" width="170" height="240" rx="3" fill="url(#blockbreaker-room)" />
            <path className="map-ring" d="M35 140 H205" />
            <path className="blockbreaker-map-danger" d={`M35 ${mapY(BLOCKBREAKER_DANGER_Z)} H205`} />
            {blockbreaker.bricks.map((brick) => (
              <rect
                className="blockbreaker-map-brick"
                key={brick.id}
                x={mapX(blockbreakerColumnX(brick.column)) - 12.5}
                y={mapY(blockbreakerRowZ(brick.row)) - 5}
                width="25"
                height="10"
                rx="2"
                fill={colors[brick.color]}
              />
            ))}
            {bossMotions.map((motion, index) => <circle key={`${motion.bossId}-${index}`} className="map-boss" cx={mapX(motion.position[0])} cy={mapY(motion.position[1])} r="7" />)}
            <circle className="map-player-pulse" cx={mapX(playerPosition[0])} cy={mapY(playerPosition[1])} r="12" />
            <circle className="map-player" cx={mapX(playerPosition[0])} cy={mapY(playerPosition[1])} r="6" />
            {(["brann", "nia", "orin", "vale"] as const).map((memberId) => <circle key={memberId} className={`map-ally map-ally-${memberId}`} cx={mapX(partyPositions[memberId][0])} cy={mapY(partyPositions[memberId][1])} r="4" />)}
            <circle className="hockey-map-puck" cx={mapX(blockbreaker.puckPosition[0])} cy={mapY(blockbreaker.puckPosition[1])} r="5" />
            {barrier.expiresAt > time && <ellipse className="map-barrier" cx={mapX(barrier.center[0])} cy={mapY(barrier.center[1])} rx={BARRIER_RADIUS * 8.5} ry={BARRIER_RADIUS * 8} />}
          </svg>
          <span className="map-state">{phase === "combat" ? `${blockbreaker.bricks.length} BRICKS · ${blockbreaker.score} SCORE` : "WALL PREVIEW"}</span>
        </div>
      </div>
    );
  }
  if (activityMode === "hockey-healing-pvp") {
    const mapX = (x: number) => 120 + x * 8.2;
    const mapY = (z: number) => 140 + z * 4.85;
    const localWorldZ = (z: number) => z + HOCKEY_PVP_SIDE_OFFSET_Z;
    const opponentWorldX = (x: number) => -x;
    const opponentWorldZ = (z: number) => -z - HOCKEY_PVP_SIDE_OFFSET_Z;
    return (
      <div className="map-panel hockey-map-panel pvp-map-panel">
        <div className="map-copy">
          <span>Healing Hockey PVP</span>
          <h2>You vs {hockeyPvp.opponentName}</h2>
          <p>Matching boss order. Each goal hits all five allies for {HOCKEY_PVP_GOAL_DAMAGE} damage.</p>
          <div className="map-legend"><i className="legend-party" /> Your party <i className="legend-boss" /> Bosses <i className="legend-opponent" /> Rival <i className="legend-exit" /> Puck</div>
        </div>
        <div className="map-canvas">
          <svg viewBox="0 0 240 280" role="img" aria-label="Healing Hockey PVP tactical map">
            <defs><linearGradient id="pvp-hockey-room" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#361819" /><stop offset="0.5" stopColor="#15262b" /><stop offset="1" stopColor="#0a3040" /></linearGradient></defs>
            <rect className="map-room hockey-map-room" x="35" y="14" width="170" height="252" rx="3" fill="url(#pvp-hockey-room)" />
            <path className="map-ring" d="M35 140 H205 M120 116 A24 24 0 1 0 120 164 A24 24 0 1 0 120 116" />
            <path className="hockey-map-goal is-npc" d={`M${mapX(-HOCKEY_PVP_GOAL_HALF_WIDTH)} ${mapY(-HOCKEY_PVP_GOAL_Z)} H${mapX(HOCKEY_PVP_GOAL_HALF_WIDTH)}`} />
            <path className="hockey-map-goal is-healer" d={`M${mapX(-HOCKEY_PVP_GOAL_HALF_WIDTH)} ${mapY(HOCKEY_PVP_GOAL_Z)} H${mapX(HOCKEY_PVP_GOAL_HALF_WIDTH)}`} />
            <circle className="map-boss" cx={mapX(bossMotion.position[0])} cy={mapY(localWorldZ(bossMotion.position[1]))} r="7" />
            <circle className="map-boss is-opponent" cx={mapX(opponentWorldX(hockeyPvpOpponent.bossMotion.position[0]))} cy={mapY(opponentWorldZ(hockeyPvpOpponent.bossMotion.position[1]))} r="7" />
            <circle className="map-player-pulse" cx={mapX(playerPosition[0])} cy={mapY(localWorldZ(playerPosition[1]))} r="10" />
            <circle className="map-player" cx={mapX(playerPosition[0])} cy={mapY(localWorldZ(playerPosition[1]))} r="5" />
            {(["brann", "nia", "orin", "vale"] as const).map((memberId) => <circle key={memberId} className={`map-ally map-ally-${memberId}`} cx={mapX(partyPositions[memberId][0])} cy={mapY(localWorldZ(partyPositions[memberId][1]))} r="3.5" />)}
            {(["aelia", "brann", "nia", "orin", "vale"] as const).map((memberId) => <circle key={`opponent-${memberId}`} className="map-ally map-opponent" cx={mapX(opponentWorldX(hockeyPvpOpponent.partyPositions[memberId][0]))} cy={mapY(opponentWorldZ(hockeyPvpOpponent.partyPositions[memberId][1]))} r="3.5" />)}
            <circle className="hockey-map-puck" cx={mapX(hockeyPvp.puckPosition[0])} cy={mapY(hockeyPvp.puckPosition[1])} r="5" />
          </svg>
          <span className="map-state">{phase === "combat" ? `GOALS ${hockeyPvp.opponentGoalsConceded}–${hockeyPvp.localGoalsConceded} · LIVE` : "VERSUS RINK"}</span>
        </div>
      </div>
    );
  }
  if (activityMode === "hockey-healing") {
    const mapX = (x: number) => 120 + x * 8.5;
    const mapY = (z: number) => 140 + (z - HOCKEY_ARENA_CENTER_Z) * 8;
    const npcGoalLeft = mapX(-HOCKEY_GOAL_HALF_WIDTH);
    const npcGoalRight = mapX(HOCKEY_GOAL_HALF_WIDTH);
    return (
      <div className="map-panel hockey-map-panel">
        <div className="map-copy">
          <span>Hockey Healing</span>
          <h2>Rectangular Boss Rink</h2>
          <p>Party fights enemy half. Moving Pong paddle tracks each return and strikes it back toward healer.</p>
          <div className="map-legend"><i className="legend-party" /> Party <i className="legend-boss" /> Boss <i className="legend-paddle" /> Paddle <i className="legend-exit" /> Puck</div>
        </div>
        <div className="map-canvas">
          <svg viewBox="0 0 240 280" role="img" aria-label="Hockey Healing tactical map">
            <defs>
              <linearGradient id="hockey-room" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#251717" /><stop offset="0.5" stopColor="#10262d" /><stop offset="1" stopColor="#092d39" /></linearGradient>
            </defs>
            <rect className="map-room hockey-map-room" x="35" y="20" width="170" height="240" rx="3" fill="url(#hockey-room)" />
            <path className="map-ring" d="M35 140 H205 M120 116 A24 24 0 1 0 120 164 A24 24 0 1 0 120 116" />
            <path className="hockey-map-goal is-npc" d={`M${npcGoalLeft} ${mapY(HOCKEY_NPC_GOAL_Z)} H${npcGoalRight}`} />
            <path className="hockey-map-goal is-healer" d={`M${npcGoalLeft} ${mapY(HOCKEY_HEALER_GOAL_Z)} H${npcGoalRight}`} />
            <rect
              className="hockey-map-paddle"
              x={mapX(hockey.paddleX - HOCKEY_NPC_PADDLE_HALF_WIDTH)}
              y={mapY(HOCKEY_NPC_PADDLE_Z) - 4}
              width={HOCKEY_NPC_PADDLE_HALF_WIDTH * 2 * 8.5}
              height="8"
              rx="3"
            />
            {bossMotions.map((motion, index) => (
              <g key={`${motion.bossId}-${index}`}>
                <circle className="map-boss" cx={mapX(motion.position[0])} cy={mapY(motion.position[1])} r="8" />
                <path className="map-boss-arrow" d={`M${mapX(motion.position[0])} ${mapY(motion.position[1]) - 14} l6 9 h-12 z`} />
              </g>
            ))}
            <circle className="map-player-pulse" cx={mapX(playerPosition[0])} cy={mapY(playerPosition[1])} r="12" />
            <circle className="map-player" cx={mapX(playerPosition[0])} cy={mapY(playerPosition[1])} r="6" />
            {(["brann", "nia", "orin", "vale"] as const).map((memberId) => (
              <circle key={memberId} className={`map-ally map-ally-${memberId}`} cx={mapX(partyPositions[memberId][0])} cy={mapY(partyPositions[memberId][1])} r="4" />
            ))}
            <circle className="hockey-map-puck" cx={mapX(hockey.puckPosition[0])} cy={mapY(hockey.puckPosition[1])} r="5" />
            {barrier.expiresAt > time && <ellipse className="map-barrier" cx={mapX(barrier.center[0])} cy={mapY(barrier.center[1])} rx={BARRIER_RADIUS * 8.5} ry={BARRIER_RADIUS * 8} />}
          </svg>
          <span className="map-state">{phase === "combat" ? `${hockey.returns} RETURNS · LIVE` : "RINK PREVIEW"}</span>
        </div>
      </div>
    );
  }
  return (
    <div className="map-panel">
      <div className="map-copy">
        <span>Ember Vault</span>
        <h2>{bossDefinition.mapTitle}</h2>
        <p>Single chamber trial. {bossDefinition.mapCopy}</p>
        <div className="map-legend"><i className="legend-party" /> Party <i className="legend-boss" /> Boss <i className="legend-exit" /> Entry</div>
      </div>
      <div className="map-canvas">
        <svg viewBox="0 0 240 280" role="img" aria-label="Ember Vault encounter map">
          <defs>
            <radialGradient id="room" cx="50%" cy="45%" r="60%"><stop offset="0" stopColor="#1b302b" /><stop offset="1" stopColor="#0a1715" /></radialGradient>
          </defs>
          <path className="map-room" d="M120 18 C181 18 218 62 218 128 C218 190 184 231 149 242 L149 271 L91 271 L91 242 C53 229 22 190 22 128 C22 62 59 18 120 18Z" fill="url(#room)" />
          <path className="map-ring" d="M120 39 C169 39 195 75 195 128 C195 181 168 219 120 219 C72 219 45 181 45 128 C45 75 71 39 120 39Z" />
          <path className="map-glyph" d="M120 74 L145 119 L120 164 L95 119 Z M76 175 L120 196 L164 175" />
          {Array.from({ length: 8 }, (_, index) => {
            const angle = (index / 8) * Math.PI * 2;
            return <circle key={index} className="map-pillar" cx={120 + Math.sin(angle) * 77} cy={128 + Math.cos(angle) * 76} r="4" />;
          })}
          {bossMotions.map((motion, index) => {
            const bossX = 120 + motion.position[0] * 7;
            const bossY = 143 + motion.position[1] * 5.3;
            return <g key={`${motion.bossId}-${index}`}>
              <circle className="map-boss" cx={bossX} cy={bossY} r="9" />
              <path className="map-boss-arrow" d={`M${bossX} ${bossY - 15} L${bossX + 6} ${bossY - 5} L${bossX - 6} ${bossY - 5} Z`} />
            </g>;
          })}
          <circle className="map-player-pulse" cx={playerX} cy={playerY} r="12" />
          <circle className="map-player" cx={playerX} cy={playerY} r="6" />
          {barrier.expiresAt > time && (
            <ellipse
              className="map-barrier"
              cx={120 + barrier.center[0] * 7}
              cy={143 + barrier.center[1] * 5.3}
              rx={BARRIER_RADIUS * 7}
              ry={BARRIER_RADIUS * 5.3}
            />
          )}
          {(["brann", "nia", "orin", "vale"] as const).map((memberId) => (
            <circle
              key={memberId}
              className={`map-ally map-ally-${memberId}`}
              data-member-id={memberId}
              cx={120 + partyPositions[memberId][0] * 7}
              cy={143 + partyPositions[memberId][1] * 5.3}
              r="4"
            />
          ))}
          <path className="map-entry" d="M103 258 L120 245 L137 258" />
        </svg>
        <span className="map-state">{phase === "combat" ? "LIVE TACTICAL FEED" : "STATIC ROUTE"}</span>
      </div>
    </div>
  );
}

function PackPanel() {
  const inventory = useGameStore((state) => state.inventory);
  const selectedItemId = useGameStore((state) => state.selectedItemId);
  const selectItem = useGameStore((state) => state.selectItem);
  const item = inventory.find((entry) => entry.id === selectedItemId) ?? inventory[0];
  return (
    <div className="pack-panel">
      <div className="item-list">
        <div className="section-label"><span>Field pack</span><small>{inventory.length} / 12</small></div>
        {inventory.map((entry) => (
          <button key={entry.id} onClick={() => selectItem(entry.id)} className={entry.id === item.id ? "is-selected" : ""}>
            <b>{entry.icon}</b><span><strong>{entry.name}</strong><small>{entry.slot}{entry.equipped ? " · Equipped" : ""}</small></span><i>{entry.rarity[0]}</i>
          </button>
        ))}
      </div>
      <article className={`item-tooltip rarity-${item.rarity.toLowerCase()}`}>
        <span className="tooltip-label">Item detail · lower display</span>
        <div className="tooltip-title"><b>{item.icon}</b><span><h2>{item.name}</h2><small>{item.rarity} {item.slot}</small></span></div>
        <div className="tooltip-stats">{item.stats.map((stat) => <strong key={stat}>{stat}</strong>)}</div>
        <p>{item.effect}</p>
        <footer><span>{item.equipped ? "✓ Equipped" : "Not equipped"}</span><small>Compare: hold Y</small></footer>
      </article>
    </div>
  );
}

function PvpPanel() {
  const opponentName = useGameStore((state) => state.hockeyPvp.opponentName);
  const opponentParty = useGameStore((state) => state.hockeyPvpOpponent.party);
  const opponentGoalsConceded = useGameStore((state) => state.hockeyPvp.opponentGoalsConceded);
  const localGoalsConceded = useGameStore((state) => state.hockeyPvp.localGoalsConceded);
  const opponentBossKills = useGameStore((state) => state.hockeyPvp.opponentBossKills);
  const living = opponentParty.filter((member) => member.hp > 0).length;
  const currentHealth = opponentParty.reduce((total, member) => total + Math.max(0, member.hp), 0);
  const maximumHealth = opponentParty.reduce((total, member) => total + member.maxHp, 0);

  return (
    <div className="pvp-panel">
      <header className="pvp-roster-header">
        <span><small>Opponent party</small><strong>{opponentName}</strong></span>
        <div><small>Goals</small><strong>{opponentGoalsConceded}–{localGoalsConceded}</strong></div>
        <div><small>Boss KOs</small><strong>{opponentBossKills}</strong></div>
        <div><small>Standing</small><strong>{living} / {opponentParty.length}</strong></div>
      </header>
      <div className="pvp-party-list" aria-label={`${opponentName} party health`}>
        <div className="section-label"><span>Rival health feed</span><small>{Math.ceil(currentHealth)} / {maximumHealth} total</small></div>
        {opponentParty.map((member) => (
          <article className={`pvp-party-frame ${member.hp <= 0 ? "is-down" : ""}`} key={member.id}>
            <span className="party-avatar" style={{ "--member-color": member.color } as React.CSSProperties}>{member.name[0]}</span>
            <span className="party-data">
              <span className="party-name"><strong>{member.name}</strong><em>{Math.ceil(member.hp)} / {member.maxHp}</em></span>
              <HealthBar member={member} />
              <small>{member.className}</small>
            </span>
            <b>{member.hp <= 0 ? "DOWN" : `${Math.ceil((member.hp / member.maxHp) * 100)}%`}</b>
          </article>
        ))}
      </div>
    </div>
  );
}

const tabPresentation: Record<BottomTab, { label: string; icon: string; key: string }> = {
  combat: { label: "Heal", icon: "✦", key: "" },
  map: { label: "Map", icon: "⌁", key: "M" },
  pack: { label: "Pack", icon: "▧", key: "I" },
  pvp: { label: "PVP", icon: "⚔", key: "P" },
};

function RpgBottomDisplay({ run, focusedId, paused, onExit }: {
  readonly run: NonNullable<ReturnType<typeof useGameStore.getState>["rpgRun"]>;
  readonly focusedId: string | null;
  readonly paused: boolean;
  readonly onExit?: () => void;
}) {
  const party = useGameStore((state) => state.party);
  const selectedMemberId = useGameStore((state) => state.selectedMemberId);
  const mana = useGameStore((state) => state.mana);
  const maxMana = useGameStore((state) => state.maxMana);
  const cooldowns = useGameStore((state) => state.cooldowns);
  const globalCooldownUntil = useGameStore((state) => state.globalCooldownUntil);
  const time = useGameStore((state) => state.time);
  const activeCast = useGameStore((state) => state.activeCast);
  const spellResources = useGameStore((state) => state.rpgSpellResources);
  const selectMember = useGameStore((state) => state.selectMember);
  const castAbility = useGameStore((state) => state.castAbility);
  const dispatchRpgAction = useGameStore((state) => state.dispatchRpgAction);
  const setRpgFocusId = useGameStore((state) => state.setRpgFocusId);
  const restart = useGameStore((state) => state.restart);
  return (
    <section className="display bottom-display rpg-bottom-display" aria-label="RPG Roguelike tactical display">
      <RpgRunTacticalPanel
        run={run}
        focusedId={focusedId}
        onFocusChange={setRpgFocusId}
        onAction={dispatchRpgAction}
        onRestartRun={restart}
        onExitRun={onExit}
        liveCombat={{
          party,
          selectedMemberId,
          mana,
          maxMana,
          cooldowns,
          globalCooldownUntil,
          time,
          activeCast,
          spellResources,
          onSelectMember: selectMember,
          onCastAbility: castAbility,
        }}
      />
      {paused && (
        <div className="lower-pause-overlay" aria-hidden="true">
          <span>PAUSED</span><strong>Expedition suspended</strong><small>{DEFAULT_CONTROLLER_GLYPHS.start} / ESC resumes · ↑↓ selects menu action</small>
        </div>
      )}
    </section>
  );
}

export function BottomScreen({ onExit }: { onExit?: () => void } = {}) {
  const activeTab = useGameStore((state) => state.activeTab);
  const setActiveTab = useGameStore((state) => state.setActiveTab);
  const phase = useGameStore((state) => state.phase);
  const paused = useGameStore((state) => state.paused);
  const runMode = useGameStore((state) => state.runMode);
  const activityMode = useGameStore((state) => state.activityMode);
  const rpgRun = useGameStore((state) => state.rpgRun);
  const rpgFocusId = useGameStore((state) => state.rpgFocusId);
  const tabs = bottomTabsFor(runMode);
  if (runMode === "rpg-roguelike" && rpgRun) {
    return <RpgBottomDisplay run={rpgRun} focusedId={rpgFocusId} paused={paused} onExit={onExit} />;
  }
  return (
    <section className="display bottom-display" aria-label="Tactical touch display">
      <header className="lower-header">
        <div className="lower-brand"><span>IH</span><strong>I Want To Heal</strong><small>{phase === "combat" ? "Encounter live" : "Field console"}</small></div>
        <nav aria-label="Lower display sections" role="tablist">
          {tabs.map((tabId) => {
            const tab = tabPresentation[tabId];
            return <button key={tabId} role="tab" aria-selected={activeTab === tabId} className={activeTab === tabId ? "is-active" : ""} onClick={() => setActiveTab(tabId)}>
              <i>{tab.icon}</i><span>{tab.label}</span>{tab.key && <small>{tab.key}</small>}
            </button>
          })}
        </nav>
      </header>
      <main className="lower-content">
        {activeTab === "combat" && <CombatPanel onExit={onExit} />}
        {activeTab === "map" && <MapPanel />}
        {activeTab === "pack" && activityMode !== "hockey-healing-pvp" && <PackPanel />}
        {activeTab === "pvp" && activityMode === "hockey-healing-pvp" && <PvpPanel />}
      </main>
      {paused && (
        <div className="lower-pause-overlay" aria-hidden="true">
          <span>PAUSED</span><strong>Encounter suspended</strong><small>{DEFAULT_CONTROLLER_GLYPHS.start} / ESC resumes · ↑↓ selects menu action</small>
        </div>
      )}
    </section>
  );
}
