import { lazy, memo, Suspense, useEffect, useRef, useState } from "react";
import { barrierProtects, healerFieldContains, upcomingEncounterMechanic, useGameStore } from "../game/store";
import { HEALER_ABILITIES } from "../game/healers";
import { BOSS_DEFINITIONS } from "../game/bossCatalog";
import { bossRoomFor } from "../game/bossRooms";
import { tankAuraProtects } from "../game/partyCombat";
import { BuffDraftPanel } from "./BuffDraftPanel";
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs";
import { hockeyPvpDampeningPercent } from "../game/hockeyHealingPvp";
import { blockbreakerTimeMultiplier } from "../game/blockbreaker";
import { RpgRunOverlay } from "./rpgRoguelike/RpgRunOverlay";
import type { CharacterAppearanceV1 } from "../game/characterAppearance";
import { healerMaxResource, isBeaconOfLightTarget } from "../game/healerMechanics";

const GameScene = memo(lazy(() => import("./GameScene").then((module) => ({ default: module.GameScene }))));
GameScene.displayName = "MemoizedGameScene";
const CONVICTION_PIPS = [0, 1, 2] as const;
const MAX_CONVICTION = healerMaxResource("paladin");

function ConvictionMeter() {
  const healerClassId = useGameStore((state) => state.healerClassId);
  const runMode = useGameStore((state) => state.runMode);
  const encounterConviction = useGameStore((state) => state.healerMechanic.resource);
  const rpgConviction = useGameStore((state) => state.rpgSpellResources.conviction);
  if (healerClassId !== "paladin") return null;
  const conviction = Math.max(0, Math.min(MAX_CONVICTION, runMode === "rpg-roguelike" ? rpgConviction : encounterConviction));
  return (
    <div
      className={`top-conviction-meter ${conviction === MAX_CONVICTION ? "is-full" : ""}`}
      role="meter"
      aria-label="Conviction"
      aria-valuemin={0}
      aria-valuemax={MAX_CONVICTION}
      aria-valuenow={conviction}
      aria-valuetext={`${conviction} of ${MAX_CONVICTION} Conviction`}
      title={`${conviction} of ${MAX_CONVICTION} Conviction`}
    >
      <span><b>Conviction</b><small>{conviction} / {MAX_CONVICTION}</small></span>
      <span className="top-conviction-pips" aria-hidden="true">
        {CONVICTION_PIPS.map((pip) => <i className={pip < conviction ? "is-filled" : ""} key={pip} />)}
      </span>
    </div>
  );
}

function CompactParty() {
  const party = useGameStore((state) => state.party);
  const time = useGameStore((state) => state.time);
  const selected = useGameStore((state) => state.selectedMemberId);
  const barrier = useGameStore((state) => state.barrier);
  const tankAura = useGameStore((state) => state.partyCombat.tankAura);
  const partyPositions = useGameStore((state) => state.partyPositions);
  const selectMember = useGameStore((state) => state.selectMember);
  const mana = useGameStore((state) => state.mana);
  const maxMana = useGameStore((state) => state.maxMana);
  const healerMechanic = useGameStore((state) => state.healerMechanic);
  return (
    <div className="top-party" aria-label="Party health and player resources">
      {party.map((member) => {
        const health = (member.hp / member.maxHp) * 100;
        const shield = (member.absorb / member.maxHp) * 100;
        const beaconed = isBeaconOfLightTarget(member.id, healerMechanic, time);
        return (
          <button
            className={`top-party-member ${selected === member.id ? "is-selected" : ""} ${beaconed ? "is-beacon" : ""}`}
            key={member.id}
            onClick={() => selectMember(member.id)}
            aria-label={`Target ${member.name}, ${Math.ceil(member.hp)} health${beaconed ? ", Beacon of Light" : ""}`}
          >
            <span className="portrait-dot" style={{ background: member.color }}>{member.name[0]}</span>
            <span className="top-party-copy"><strong>{member.name}</strong><small>{member.role}</small></span>
            <span className={`microbar ${member.id === "aelia" ? "player-health-bar" : ""}`}>
              <i style={{ width: `${health}%` }} />
              {shield > 0 && <b style={{ width: `${Math.max(8, shield)}%` }} />}
            </span>
            {member.id === "aelia" && (
              <span className="player-mana-bar" aria-label={`${Math.ceil(mana)} of ${maxMana} mana`}>
                <i style={{ width: `${(mana / maxMana) * 100}%` }} />
              </span>
            )}
            <span className="top-effects">
              {beaconed && <em className="beacon-pip" title={`Beacon of Light: ${Math.max(0, healerMechanic.beaconExpiresAt - time).toFixed(1)} seconds`}>✦</em>}
              {member.healingEffects.some((effect) => effect.expiresAt > time) && <em className="renew-pip">H</em>}
              {member.reactiveHeal && member.reactiveHeal.expiresAt > time && <em className="renew-pip">E{member.reactiveHeal.charges}</em>}
              {member.debuffs.length > 0 && <em className="debuff-pip">!</em>}
              {barrierProtects(partyPositions[member.id], barrier, time) && <em className="barrier-pip">B</em>}
              {barrier.kind === "spirit-link" && healerFieldContains(partyPositions[member.id], barrier, time) && <em className="barrier-pip">S</em>}
              {tankAuraProtects(partyPositions[member.id], partyPositions[tankAura.sourceId], tankAura, time) && <em className="tank-aura-pip">T</em>}
              {member.knockedUntil > time && <em className="knockdown-pip">KD</em>}
            </span>
          </button>
        );
      })}
      <ConvictionMeter />
    </div>
  );
}

function CastingBar() {
  const activeCast = useGameStore((state) => state.activeCast);
  const time = useGameStore((state) => state.time);
  const party = useGameStore((state) => state.party);
  if (!activeCast) return null;
  const abilityName = HEALER_ABILITIES[activeCast.abilityId].name;
  const duration = activeCast.completesAt - activeCast.startedAt;
  const progress = Math.min(1, Math.max(0, (time - activeCast.startedAt) / duration));
  const target = party.find((member) => member.id === activeCast.targetId);
  return (
    <div className="casting-bar" aria-label={`Casting ${abilityName} on ${target?.name ?? "ally"}`}>
      <span><strong>{abilityName}</strong><small>{target?.name}</small></span>
      <b>{Math.max(0, activeCast.completesAt - time).toFixed(1)}s</b>
      <i><em style={{ width: `${progress * 100}%` }} /></i>
    </div>
  );
}

function BossBar() {
  const boss = useGameStore((state) => state.boss);
  const additionalBosses = useGameStore((state) => state.additionalBosses);
  const opponentBoss = useGameStore((state) => state.hockeyPvpOpponent.boss);
  const phase = useGameStore((state) => state.phase);
  const activityMode = useGameStore((state) => state.activityMode);
  if (phase === "briefing") return null;
  const hockeyMode = activityMode === "hockey-healing";
  const blockbreakerMode = activityMode === "blockbreaker";
  const aetherAssaultMode = activityMode === "aether-assault";
  const pvpMode = activityMode === "hockey-healing-pvp";
  const bosses = pvpMode ? [boss, opponentBoss] : [boss, ...additionalBosses.map((entry) => entry.boss)];
  return (
    <div className={`boss-bar-wrap ${bosses.length > 1 ? "is-multi" : ""} ${bosses.length === 3 ? "is-trio" : ""} ${pvpMode ? "is-pvp" : ""}`}>
      {bosses.map((entry, index) => <div className={`boss-bar-entry ${pvpMode && index === 1 ? "is-opponent" : ""}`} key={`${entry.id}-${index}`}>
        <div className="boss-name"><span>{pvpMode ? index === 0 ? "Your target" : "Rival target" : hockeyMode ? index === 0 ? "Striker · paddle + damage" : "Frontline · party damage" : blockbreakerMode ? index === 0 ? "Breaker flank" : "Wall pressure" : aetherAssaultMode ? index === 0 ? "Arcade flank" : "Party pressure" : "Vault Beast"}</span><strong>{entry.name}</strong><span>{Math.ceil((entry.hp / entry.maxHp) * 100)}%</span></div>
        <div className="boss-bar"><i style={{ width: `${(entry.hp / entry.maxHp) * 100}%` }} /></div>
      </div>)}
    </div>
  );
}

function EncounterCallout() {
  const phase = useGameStore((state) => state.phase);
  const time = useGameStore((state) => state.time);
  const boss = useGameStore((state) => state.boss);
  const bossMotion = useGameStore((state) => state.bossMotion);
  const additionalBosses = useGameStore((state) => state.additionalBosses);
  if (phase !== "combat") return null;
  const mechanic = upcomingEncounterMechanic({ boss, bossMotion, additionalBosses, time });
  return (
    <div className={`encounter-callout ${mechanic.urgent ? "is-urgent" : ""}`}>
      <span>{mechanic.name}</span>
      <strong>{mechanic.remaining.toFixed(1)}s</strong>
    </div>
  );
}

function PhaseOverlay() {
  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 primaryBoss = useGameStore((state) => state.boss);
  const additionalBosses = useGameStore((state) => state.additionalBosses);
  const hockey = useGameStore((state) => state.hockey);
  const blockbreaker = useGameStore((state) => state.blockbreaker);
  const aetherAssault = useGameStore((state) => state.aetherAssault);
  const hockeyPvp = useGameStore((state) => state.hockeyPvp);
  if (runMode === "rpg-roguelike") return null;
  if (phase === "intermission") return <BuffDraftPanel className="top-buff-draft" />;
  const bosses = [primaryBoss, ...additionalBosses.map((entry) => entry.boss)];
  const definitions = bosses.map((boss) => BOSS_DEFINITIONS[boss.id]);
  const room = bossRoomFor(primaryBoss.id);
  const bossNames = bosses.map((boss) => boss.name).join(" & ");
  const hockeyMode = activityMode === "hockey-healing";
  const blockbreakerMode = activityMode === "blockbreaker";
  const aetherAssaultMode = activityMode === "aether-assault";
  const pvpMode = activityMode === "hockey-healing-pvp";
  const showEndlessChoice = phase === "victory" && runMode === "rogue-trials" && round === 5 && !endlessMode;
  const endlessDefeat = phase === "defeat" && endlessMode && !hockeyMode && !blockbreakerMode && !aetherAssaultMode;
  const briefingMode = hockeyMode
    ? "Endless Goal Defense"
    : blockbreakerMode
    ? "Endless Color Break"
    : aetherAssaultMode
    ? "Endless Arcade Assault"
    : pvpMode
    ? `Versus ${hockeyPvp.opponentName}`
    : runMode === "rogue-trials"
    ? bosses.length === 3 ? "Rogue Trials · Trio Finale" : "Rogue Trials · Dual Round"
    : bosses.length > 1 ? "Roguelike PVE · Dual Encounter" : definitions[0].trial;
  if (phase === "combat") return null;
  const title = phase === "briefing"
    ? hockeyMode ? "Hockey Healing" : blockbreakerMode ? "Blockbreaker" : aetherAssaultMode ? "Aether Assault" : pvpMode ? "Healing Hockey PVP" : room.name
    : phase === "victory"
        ? showEndlessChoice ? "Rogue Trials Cleared" : pvpMode ? "Match Won" : `${bossNames} Broken`
        : hockeyMode ? "Goal Breached" : blockbreakerMode && blockbreaker.status === "lost" ? "Wall Breached" : aetherAssaultMode ? "Formation Lost" : pvpMode ? "Match Lost" : endlessDefeat ? "Endless Run Ended" : "Party Broken";
  const eyebrow = phase === "briefing"
    ? hockeyMode ? `${briefingMode} · Rectangular Boss Rink` : blockbreakerMode ? `${briefingMode} · Advancing Brick Rink` : aetherAssaultMode ? `${briefingMode} · Bright Five-Lane Rink` : pvpMode ? `${briefingMode} · Extended Versus Rink` : `${briefingMode} · ${room.biome}`
    : phase === "victory"
        ? showEndlessChoice ? "Endless Path Unlocked" : pvpMode ? `${hockeyPvp.opponentGoalsConceded} Rival Goals · ${endlessBossKills} Boss Kills` : "Encounter Complete"
        : hockeyMode ? `${hockey.returns} Pucks Returned · ${endlessBossKills} Bosses Defeated` : blockbreakerMode ? `${blockbreaker.bricksBroken} Bricks · ${blockbreaker.score.toLocaleString()} Points` : aetherAssaultMode ? `${aetherAssault.score.toLocaleString()} Points · Wave ${aetherAssault.wave}` : pvpMode ? `${hockeyPvp.localGoalsConceded} Goals Conceded · ${hockeyPvp.opponentBossKills} Rival Boss Kills` : endlessDefeat ? `${endlessBossKills} Endless Bosses Defeated` : "Encounter Failed";
  const copy = phase === "briefing"
    ? hockeyMode
      ? "Defend the wide blue goal. Aim each return; the moving Pong paddle strikes it back. Party fights both bosses on enemy half. Fallen bosses are replaced."
      : blockbreakerMode
      ? "Aim the puck into advancing five-brick rows. Matching orthogonal colors break as one combo while two bosses pressure the party. Missed pucks safely re-serve."
      : aetherAssaultMode
      ? "Move across the full bright rink while spellfire launches automatically. Line up ship formations, dodge red bolts and dives, and keep healing through two endless bosses."
      : pvpMode
      ? "Two parties fight matching boss sequences. Defend your goal and aim each return. Every goal deals 45 damage to all five players. Fallen bosses respawn instantly."
      : definitions.map((boss) => boss.briefing).join(" ")
    : phase === "victory"
      ? showEndlessChoice ? "Leave with the clear, or continue against an unbroken chain of replacement bosses." : pvpMode ? `${hockeyPvp.opponentName}'s party fell first.` : "Five entered. Five endured."
      : hockeyMode
        ? `Run ended after ${hockey.returns} returns and ${endlessBossKills} boss kills.`
        : blockbreakerMode
        ? `Run record: ${blockbreaker.bricksBroken} bricks, ${blockbreaker.score.toLocaleString()} points, and ${endlessBossKills} boss kills.`
        : aetherAssaultMode
        ? `Run record: ${aetherAssault.score.toLocaleString()} points, wave ${aetherAssault.wave}, ${aetherAssault.kills} ships, and ${endlessBossKills} boss kills.`
        : pvpMode ? `${hockeyPvp.opponentName} kept their party standing.`
        : endlessDefeat ? `Run record: ${endlessBossKills} bosses defeated after the trio finale.` : definitions.map((boss) => boss.failure).join(" ");
  return (
    <div className={`phase-overlay phase-${phase}`}>
      <div className="phase-sigil">✦</div>
      <span>{eyebrow}</span>
      <h1>{title}</h1>
      <p>{copy}</p>
      <small>{phase === "briefing" ? "Begin from lower display" : showEndlessChoice ? "Choose Endless Mode or Quit on lower display" : pvpMode ? "Choose next match from lower display" : "Restart from lower display"}</small>
    </div>
  );
}

function BlockbreakerScorePopup() {
  const activityMode = useGameStore((state) => state.activityMode);
  const lastBreakAt = useGameStore((state) => state.blockbreaker.lastBreakAt);
  const count = useGameStore((state) => state.blockbreaker.lastBreakCount);
  const award = useGameStore((state) => state.blockbreaker.lastScoreAward);
  const [visibleAt, setVisibleAt] = useState<number | null>(null);

  useEffect(() => {
    if (activityMode !== "blockbreaker" || !Number.isFinite(lastBreakAt)) {
      setVisibleAt(null);
      return;
    }
    setVisibleAt(lastBreakAt);
    const timeout = window.setTimeout(() => setVisibleAt((current) => current === lastBreakAt ? null : current), 850);
    return () => window.clearTimeout(timeout);
  }, [activityMode, lastBreakAt]);

  if (visibleAt === null) return null;
  return <div className="blockbreaker-score-popup" role="status" aria-live="polite"><strong>+{award.toLocaleString()}</strong><small>{count} {count === 1 ? "brick" : "brick combo"}</small></div>;
}

function AetherScorePopup() {
  const activityMode = useGameStore((state) => state.activityMode);
  const lastKillAt = useGameStore((state) => state.aetherAssault.lastKillAt);
  const award = useGameStore((state) => state.aetherAssault.lastKillScore);
  const multiplier = useGameStore((state) => state.aetherAssault.multiplier);
  const [visibleAt, setVisibleAt] = useState<number | null>(null);

  useEffect(() => {
    if (activityMode !== "aether-assault" || !Number.isFinite(lastKillAt)) {
      setVisibleAt(null);
      return;
    }
    setVisibleAt(lastKillAt);
    const timeout = window.setTimeout(() => setVisibleAt((current) => current === lastKillAt ? null : current), 700);
    return () => window.clearTimeout(timeout);
  }, [activityMode, lastKillAt]);

  if (visibleAt === null) return null;
  return <div className="blockbreaker-score-popup aether-score-popup" role="status" aria-live="polite"><strong>+{award.toLocaleString()}</strong><small>{multiplier.toFixed(2)}× streak</small></div>;
}

function PauseOverlay({ onExit }: { onExit?: () => void }) {
  const paused = useGameStore((state) => state.paused);
  const selection = useGameStore((state) => state.pauseSelection);
  const setPaused = useGameStore((state) => state.setPaused);
  const setPauseSelection = useGameStore((state) => state.setPauseSelection);
  if (!paused) return null;
  const exit = () => {
    setPaused(false);
    onExit?.();
  };
  return (
    <div className="pause-overlay" role="dialog" aria-modal="true" aria-label="Game paused">
      <div className="pause-panel">
        <span>Encounter suspended</span>
        <h1>Paused</h1>
        <p>Simulation, damage, and movement are stopped.</p>
        <div className="pause-actions">
          <button
            className={selection === "resume" ? "is-controller-selected" : ""}
            onPointerEnter={() => setPauseSelection("resume")}
            onClick={() => setPaused(false)}
          >Resume <small>{DEFAULT_CONTROLLER_GLYPHS.start} / ESC</small></button>
          <button
            className={`secondary ${selection === "exit" ? "is-controller-selected" : ""}`}
            onPointerEnter={() => setPauseSelection("exit")}
            onClick={exit}
          >Return to main menu <small>{DEFAULT_CONTROLLER_GLYPHS.confirm}</small></button>
        </div>
        <footer><b>↑ / ↓</b> Choose <i /> <b>{DEFAULT_CONTROLLER_GLYPHS.confirm} / ENTER</b> Confirm</footer>
      </div>
    </div>
  );
}

function GoalPopup() {
  const activityMode = useGameStore((state) => state.activityMode);
  const goalSequence = useGameStore((state) => state.hockeyPvp.goalSequence);
  const lastGoalSide = useGameStore((state) => state.hockeyPvp.lastGoalSide);
  const lastSeenSequence = useRef(goalSequence);
  const [visibleSequence, setVisibleSequence] = useState<number | null>(null);

  useEffect(() => {
    if (activityMode !== "hockey-healing-pvp" || goalSequence < lastSeenSequence.current) {
      lastSeenSequence.current = goalSequence;
      setVisibleSequence(null);
      return;
    }
    if (goalSequence === lastSeenSequence.current) return;
    lastSeenSequence.current = goalSequence;
    setVisibleSequence(goalSequence);
    const timeout = window.setTimeout(() => {
      setVisibleSequence((current) => current === goalSequence ? null : current);
    }, 1_250);
    return () => window.clearTimeout(timeout);
  }, [activityMode, goalSequence]);

  if (visibleSequence === null) return null;
  return (
    <div
      key={visibleSequence}
      className={`goal-popup ${lastGoalSide === "local" ? "is-conceded" : "is-scored"}`}
      role="status"
      aria-live="assertive"
      aria-atomic="true"
    >
      <strong>GOAL</strong>
    </div>
  );
}

function DampeningIndicator() {
  const activityMode = useGameStore((state) => state.activityMode);
  const localBossKills = useGameStore((state) => state.endlessBossKills);
  const opponentBossKills = useGameStore((state) => state.hockeyPvp.opponentBossKills);
  if (activityMode !== "hockey-healing-pvp") return null;
  const percent = hockeyPvpDampeningPercent(localBossKills, opponentBossKills);
  return (
    <div className="dampening-indicator" role="status" aria-label={`${percent}% healing dampening`}>
      <span>Dampening</span><strong>{percent}%</strong>
      <i><em style={{ width: `${percent}%` }} /></i>
    </div>
  );
}

export function TopScreen({
  onExit,
  playerAppearance,
}: {
  onExit?: () => void;
  playerAppearance?: CharacterAppearanceV1;
}) {
  const phase = useGameStore((state) => state.phase);
  const bossCount = useGameStore((state) => state.additionalBosses.length + 1);
  const round = useGameStore((state) => state.round);
  const runMode = useGameStore((state) => state.runMode);
  const activityMode = useGameStore((state) => state.activityMode);
  const endlessMode = useGameStore((state) => state.endlessMode);
  const endlessBossKills = useGameStore((state) => state.endlessBossKills);
  const hockeyReturns = useGameStore((state) => state.hockey.returns);
  const blockbreaker = useGameStore((state) => state.blockbreaker);
  const aetherAssault = useGameStore((state) => state.aetherAssault);
  const hockeyPvp = useGameStore((state) => state.hockeyPvp);
  const time = useGameStore((state) => state.time);
  const setPaused = useGameStore((state) => state.setPaused);
  const rpgRun = useGameStore((state) => state.rpgRun);
  const rpgFocusId = useGameStore((state) => state.rpgFocusId);
  const dispatchRpgAction = useGameStore((state) => state.dispatchRpgAction);
  const setRpgFocusId = useGameStore((state) => state.setRpgFocusId);
  const restart = useGameStore((state) => state.restart);
  const hockeyMode = activityMode === "hockey-healing";
  const blockbreakerMode = activityMode === "blockbreaker";
  const aetherAssaultMode = activityMode === "aether-assault";
  const pvpMode = activityMode === "hockey-healing-pvp";
  const duration = `${Math.floor(time / 60)}:${String(Math.floor(time % 60)).padStart(2, "0")}`;
  return (
    <section className="display top-display" aria-label="Main game viewport">
      <Suspense fallback={<div className="scene-loading" aria-label="Loading 3D scene" />}>
        <GameScene playerAppearance={playerAppearance} />
      </Suspense>
      <div className="top-vignette" />
      <div className="top-hud">
        <CompactParty />
        <BossBar />
        <div className={`objective-chip ${hockeyMode || pvpMode || blockbreakerMode || aetherAssaultMode ? "is-hockey" : ""} ${pvpMode ? "is-pvp" : ""} ${blockbreakerMode ? "is-blockbreaker" : ""} ${aetherAssaultMode ? "is-aether" : ""}`}><span>{hockeyMode ? `Hockey Healing · ${hockeyReturns} returns · ${duration}` : blockbreakerMode ? `Blockbreaker · ${blockbreaker.score.toLocaleString()} pts · ${duration}` : aetherAssaultMode ? `Aether Assault · ${aetherAssault.score.toLocaleString()} pts · ${duration}` : pvpMode ? `VS ${hockeyPvp.opponentName} · ${duration}` : endlessMode ? `Endless · ${endlessBossKills} kills` : runMode !== "encounter" ? `Round ${round}` : "Objective"}</span><strong>{hockeyMode ? `Defend wide goal · ${endlessBossKills} boss kills` : blockbreakerMode ? `${blockbreaker.bricksBroken} bricks · ${blockbreakerTimeMultiplier(time).toFixed(1)}× · row in ${Math.max(0, blockbreaker.nextRowAt - time).toFixed(1)}s` : aetherAssaultMode ? `Wave ${aetherAssault.wave} · ${aetherAssault.ships.length} ships · ${aetherAssault.multiplier.toFixed(2)}×` : pvpMode ? `Goals ${hockeyPvp.opponentGoalsConceded}–${hockeyPvp.localGoalsConceded} · Bosses ${endlessBossKills}–${hockeyPvp.opponentBossKills}` : endlessMode ? "Defeat bosses · replacements incoming" : bossCount === 3 ? "Defeat trio · keep five alive" : bossCount === 2 ? "Defeat both · keep five alive" : "Keep all five alive"}</strong></div>
        <EncounterCallout />
        <DampeningIndicator />
        <CastingBar />
        <div className="control-hint"><b>WASD</b> Move{aetherAssaultMode ? " + auto-fire" : ""} <i /> <b>Q / E</b> Target <i /> <b>1–6</b> Cast</div>
        {onExit && <button className="game-menu-button" onClick={() => phase === "combat" ? setPaused(true) : onExit()}><b>☰</b> Menu <small>{DEFAULT_CONTROLLER_GLYPHS.start} / ESC</small></button>}
      </div>
      <GoalPopup />
      <BlockbreakerScorePopup />
      <AetherScorePopup />
      <PhaseOverlay />
      {runMode === "rpg-roguelike" && rpgRun && (
        <RpgRunOverlay
          run={rpgRun}
          focusedId={rpgFocusId}
          onFocusChange={setRpgFocusId}
          onAction={dispatchRpgAction}
          onRestartRun={restart}
          onExitRun={onExit}
        />
      )}
      <PauseOverlay onExit={onExit} />
    </section>
  );
}
