import { useFrame } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
import { Component, Suspense, useEffect, useLayoutEffect, useMemo, useRef, type ReactNode } from "react";
import * as THREE from "three";
import { ARENA_CENTER, ARENA_SIZE_MULTIPLIER, ARENA_WALL_RADIUS } from "../game/arena";
import {
  BLOCKBREAKER_BIOMES,
  blockbreakerBiomeForSeed,
  type BlockbreakerArenaBiome,
  type BlockbreakerBiomeFixture,
} from "../game/blockbreakerBiomes";
import { bossRoomFor, type BossRoomDefinition, type BossRoomFloor } from "../game/bossRooms";
import {
  HOCKEY_ARENA_CENTER_Z,
  HOCKEY_ARENA_LENGTH,
  HOCKEY_ARENA_MAX_X,
  HOCKEY_ARENA_MAX_Z,
  HOCKEY_ARENA_MIN_X,
  HOCKEY_ARENA_MIN_Z,
  HOCKEY_ARENA_WIDTH,
  HOCKEY_GOAL_HALF_WIDTH,
  HOCKEY_HEALER_GOAL_Z,
  HOCKEY_MIDLINE_Z,
  HOCKEY_NPC_GOAL_Z,
} from "../game/hockeyHealing";
import { useGameStore } from "../game/store";
import { LEGACY_GAME_ASSETS_FORCED, useGameGLTF } from "./GameAssetProvider";
import {
  HOCKEY_PVP_ARENA_MAX_X,
  HOCKEY_PVP_ARENA_MAX_Z,
  HOCKEY_PVP_ARENA_MIN_X,
  HOCKEY_PVP_ARENA_MIN_Z,
  HOCKEY_PVP_GOAL_HALF_WIDTH,
  HOCKEY_PVP_GOAL_Z,
} from "../game/hockeyHealingPvp";
import { RpgRoomPortals } from "./rpgRoguelike/RpgRoomPortals";

const ROOM_CENTER_Z = ARENA_CENTER[1];
const KAYKIT_DUNGEON_PILLAR_URL = new URL("../assets/game/models/original/environment/kaykit-dungeon/pillar-decorated.glb", import.meta.url).href;
const KAYKIT_DUNGEON_WALL_URL = new URL("../assets/game/models/original/environment/kaykit-dungeon/wall-pillar.glb", import.meta.url).href;
const KAYKIT_DUNGEON_TORCH_URL = new URL("../assets/game/models/original/environment/kaykit-dungeon/torch-lit.glb", import.meta.url).href;
const KAYKIT_DUNGEON_KIT_URL = new URL("../assets/game/models/original/environment/kaykit-dungeon/dungeon-kit-uastc.glb", import.meta.url).href;
const DUNGEON_MESH_NAMES = {
  pillar: "pillar_decorated",
  wall: "wall_pillar",
  torch: "torch_lit",
} as const;

type ArenaFixture = {
  position: readonly [number, number, number];
  rotationY: number;
  scale: number;
  scaleX?: number;
  scaleZ?: number;
  scaleY?: number;
};

const ARENA_WALL_SEGMENTS: readonly ArenaFixture[] = Array.from({ length: 16 }, (_, index) => {
  const angle = (index / 16) * Math.PI * 2;
  return {
    position: [Math.sin(angle) * ARENA_WALL_RADIUS, 0, ROOM_CENTER_Z + Math.cos(angle) * ARENA_WALL_RADIUS] as const,
    rotationY: angle,
    scale: 1,
    scaleX: ARENA_SIZE_MULTIPLIER,
  };
});
const ARENA_COLUMNS: readonly ArenaFixture[] = Array.from({ length: 8 }, (_, index) => {
  const angle = (index / 8) * Math.PI * 2 + Math.PI / 8;
  return {
    position: [Math.sin(angle) * 8.2 * ARENA_SIZE_MULTIPLIER, 0, ROOM_CENTER_Z + Math.cos(angle) * 8.2 * ARENA_SIZE_MULTIPLIER] as const,
    rotationY: angle + Math.PI / 2,
    scale: 0.9,
  };
});
const ARENA_TORCH_COLORS = [
  new THREE.Color("#ffb24d"),
  new THREE.Color("#ff7048"),
  new THREE.Color("#f7dd88"),
  new THREE.Color("#72d6ce"),
] as const;
const ARENA_TORCHES: readonly ArenaFixture[] = ARENA_COLUMNS.map((column) => ({
  position: [column.position[0], 3.45, column.position[2]] as const,
  rotationY: column.rotationY,
  scale: 0.9,
}));
const SCENERY_SLOTS = Array.from({ length: 10 }, (_, index) => {
  const angle = (index / 10) * Math.PI * 2 + Math.PI / 10;
  return { angle, x: Math.sin(angle) * 8.45 * ARENA_SIZE_MULTIPLIER, z: ROOM_CENTER_Z + Math.cos(angle) * 8.45 * ARENA_SIZE_MULTIPLIER };
});

type MarkPattern = "rays" | "rings" | "cross" | "pools";

const ROOM_PATTERNS: Record<BossRoomFloor, MarkPattern> = {
  cinder: "rays",
  desert: "rings",
  tide: "pools",
  reliquary: "rings",
  royal: "cross",
  prism: "rays",
  storm: "rays",
  wilds: "rings",
  void: "rays",
  mire: "pools",
  quarry: "rays",
  junkyard: "cross",
  web: "rings",
  moon: "rings",
  frost: "rays",
  warcamp: "cross",
  sky: "pools",
};

type SceneryKind = "crystal" | "obelisk" | "coral" | "grave" | "crown" | "shard" | "lightning" | "tree" | "rift" | "reed" | "boulder" | "scrap" | "web" | "moonstone" | "ice" | "banner" | "cloud";

const ROOM_SCENERY: Record<BossRoomFloor, SceneryKind> = {
  cinder: "crystal",
  desert: "obelisk",
  tide: "coral",
  reliquary: "grave",
  royal: "crown",
  prism: "shard",
  storm: "lightning",
  wilds: "tree",
  void: "rift",
  mire: "reed",
  quarry: "boulder",
  junkyard: "scrap",
  web: "web",
  moon: "moonstone",
  frost: "ice",
  warcamp: "banner",
  sky: "cloud",
};

function firstMesh(scene: THREE.Object3D) {
  let mesh: THREE.Mesh<THREE.BufferGeometry, THREE.Material | THREE.Material[]> | undefined;
  scene.traverse((child) => {
    if (!mesh && child instanceof THREE.Mesh) mesh = child;
  });
  if (!mesh) throw new Error("Dungeon arena asset has no mesh.");
  return mesh as THREE.Mesh<THREE.BufferGeometry, THREE.Material | THREE.Material[]>;
}

function DungeonMeshInstances({
  mesh,
  fixtures,
  tint,
  opacity = 1,
  colors,
}: {
  mesh: THREE.Mesh<THREE.BufferGeometry, THREE.Material | THREE.Material[]>;
  fixtures: readonly ArenaFixture[];
  tint: string;
  opacity?: number;
  colors?: readonly THREE.Color[];
}) {
  const sourceMaterial = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
  const material = useMemo(() => {
    const next = sourceMaterial.clone();
    if ("color" in next) (next as THREE.MeshStandardMaterial).color.set(tint);
    next.transparent = opacity < 1;
    next.opacity = opacity;
    next.depthWrite = opacity >= 1;
    return next;
  }, [opacity, sourceMaterial, tint]);
  const instances = useRef<THREE.InstancedMesh>(null);

  useEffect(() => () => material.dispose(), [material]);
  useLayoutEffect(() => {
    if (!instances.current) return;
    const dummy = new THREE.Object3D();
    fixtures.forEach((fixture, index) => {
      dummy.position.fromArray(fixture.position);
      dummy.rotation.set(0, fixture.rotationY, 0);
      dummy.scale.set(
        fixture.scale * (fixture.scaleX ?? 1),
        fixture.scale * (fixture.scaleY ?? 1),
        fixture.scale * (fixture.scaleZ ?? 1),
      );
      dummy.updateMatrix();
      instances.current!.setMatrixAt(index, dummy.matrix);
      if (colors) instances.current!.setColorAt(index, colors[index % colors.length]);
    });
    instances.current.instanceMatrix.needsUpdate = true;
    if (instances.current.instanceColor) instances.current.instanceColor.needsUpdate = true;
    instances.current.computeBoundingSphere();
  }, [colors, fixtures]);

  return <instancedMesh ref={instances} args={[mesh.geometry, material, fixtures.length]} castShadow receiveShadow />;
}

function LegacyDungeonAssetInstances({
  url,
  ...props
}: Omit<Parameters<typeof DungeonMeshInstances>[0], "mesh"> & { url: string }) {
  const gltf = useGLTF(url, false, true);
  const mesh = useMemo(() => firstMesh(gltf.scene), [gltf.scene]);
  return <DungeonMeshInstances mesh={mesh} {...props} />;
}

function arenaWalls(room: BossRoomDefinition, portalOpenings = false) {
  return ARENA_WALL_SEGMENTS.filter((_, index) => !portalOpenings || index !== 0 && index !== 8).map((fixture) => ({
    ...fixture,
    scaleY: room.wallHeight / 4,
  }));
}

function LegacyArenaArchitecture({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  const walls = useMemo(() => arenaWalls(room, portalOpenings), [portalOpenings, room]);
  return (
    <group>
      <LegacyDungeonAssetInstances url={KAYKIT_DUNGEON_WALL_URL} fixtures={walls} tint={room.wallColor} opacity={0.54} />
      <LegacyDungeonAssetInstances url={KAYKIT_DUNGEON_PILLAR_URL} fixtures={ARENA_COLUMNS} tint={room.wallColor} />
      <LegacyDungeonAssetInstances url={KAYKIT_DUNGEON_TORCH_URL} fixtures={ARENA_TORCHES} tint="#ffffff" colors={ARENA_TORCH_COLORS} />
    </group>
  );
}

function namedMesh(scene: THREE.Object3D, name: string) {
  const object = scene.getObjectByName(name);
  if (object instanceof THREE.Mesh) {
    return object as THREE.Mesh<THREE.BufferGeometry, THREE.Material | THREE.Material[]>;
  }
  throw new Error(`Dungeon kit is missing mesh ${name}.`);
}

function DungeonKitArchitecture({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  const gltf = useGameGLTF(KAYKIT_DUNGEON_KIT_URL);
  const walls = useMemo(() => arenaWalls(room, portalOpenings), [portalOpenings, room]);
  const meshes = useMemo(() => ({
    pillar: namedMesh(gltf.scene, DUNGEON_MESH_NAMES.pillar),
    wall: namedMesh(gltf.scene, DUNGEON_MESH_NAMES.wall),
    torch: namedMesh(gltf.scene, DUNGEON_MESH_NAMES.torch),
  }), [gltf.scene]);
  return (
    <group>
      <DungeonMeshInstances mesh={meshes.wall} fixtures={walls} tint={room.wallColor} opacity={0.54} />
      <DungeonMeshInstances mesh={meshes.pillar} fixtures={ARENA_COLUMNS} tint={room.wallColor} />
      <DungeonMeshInstances mesh={meshes.torch} fixtures={ARENA_TORCHES} tint="#ffffff" colors={ARENA_TORCH_COLORS} />
    </group>
  );
}

class DungeonAssetErrorBoundary extends Component<{
  children: ReactNode;
  fallback: ReactNode;
}, { failed: boolean }> {
  state = { failed: false };

  static getDerivedStateFromError() {
    return { failed: true };
  }

  componentDidCatch(error: unknown) {
    console.warn("Optimized dungeon asset failed; using legacy GLBs.", error);
  }

  render() {
    return this.state.failed ? this.props.fallback : this.props.children;
  }
}

function RoomWallFallback({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  const walls = useRef<THREE.Group>(null);
  const previousCameraPosition = useRef<THREE.Vector3 | null>(null);
  const fixtures = useMemo(
    () => ARENA_WALL_SEGMENTS.filter((_, index) => !portalOpenings || index !== 0 && index !== 8),
    [portalOpenings],
  );

  useFrame(({ camera }) => {
    if (!walls.current) return;
    const previous = previousCameraPosition.current;
    if (previous && previous.distanceToSquared(camera.position) < 0.0001) return;
    const cameraPosition = previous ?? new THREE.Vector3();
    cameraPosition.copy(camera.position);
    previousCameraPosition.current = cameraPosition;
    for (const child of walls.current.children) {
      const material = (child as THREE.Mesh<THREE.BufferGeometry, THREE.MeshStandardMaterial>).material;
      const distance = Math.hypot(camera.position.x - child.position.x, camera.position.z - child.position.z);
      material.opacity = THREE.MathUtils.smoothstep(distance, 2.5, 7.5) * 0.48 + 0.05;
    }
  });

  return (
    <group ref={walls}>
      {fixtures.map((fixture, index) => (
        <mesh
          key={index}
          position={[fixture.position[0], room.wallHeight / 2, fixture.position[2]]}
          rotation={[0, fixture.rotationY, 0]}
          receiveShadow
        >
          <boxGeometry args={[3.86 * ARENA_SIZE_MULTIPLIER, room.wallHeight, 0.22]} />
          <meshStandardMaterial color={room.wallColor} roughness={0.9} transparent opacity={0.53} depthWrite={false} />
        </mesh>
      ))}
    </group>
  );
}

function LegacyRoomWalls({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  return (
    <Suspense fallback={<RoomWallFallback room={room} portalOpenings={portalOpenings} />}>
      <LegacyArenaArchitecture room={room} portalOpenings={portalOpenings} />
    </Suspense>
  );
}

function OptimizedRoomWalls({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  return (
    <DungeonAssetErrorBoundary fallback={<LegacyRoomWalls room={room} portalOpenings={portalOpenings} />}>
      <Suspense fallback={<RoomWallFallback room={room} portalOpenings={portalOpenings} />}>
        <DungeonKitArchitecture room={room} portalOpenings={portalOpenings} />
      </Suspense>
    </DungeonAssetErrorBoundary>
  );
}

function RoomWalls({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  return LEGACY_GAME_ASSETS_FORCED
    ? <LegacyRoomWalls room={room} portalOpenings={portalOpenings} />
    : <OptimizedRoomWalls room={room} portalOpenings={portalOpenings} />;
}

function RoomMarks({ room }: { room: BossRoomDefinition }) {
  const rays = useRef<THREE.InstancedMesh>(null);
  const pattern = ROOM_PATTERNS[room.floor];

  useLayoutEffect(() => {
    if (!rays.current || pattern === "rings" || pattern === "pools") return;
    const dummy = new THREE.Object3D();
    const count = pattern === "cross" ? 4 : 8;
    for (let index = 0; index < count; index += 1) {
      const angle = (index / count) * Math.PI * 2 + (pattern === "cross" ? Math.PI / 4 : 0);
      const radius = (pattern === "cross" ? 3 : 4.3) * ARENA_SIZE_MULTIPLIER;
      dummy.position.set(Math.sin(angle) * radius, 0.025, ROOM_CENTER_Z + Math.cos(angle) * radius);
      dummy.rotation.set(0, -angle, 0);
      dummy.scale.set(
        (pattern === "cross" ? 1.6 : 0.75) * ARENA_SIZE_MULTIPLIER,
        1,
        (pattern === "cross" ? 0.48 : 2.6) * ARENA_SIZE_MULTIPLIER,
      );
      dummy.updateMatrix();
      rays.current.setMatrixAt(index, dummy.matrix);
    }
    rays.current.instanceMatrix.needsUpdate = true;
  }, [pattern]);

  if (pattern === "rings") {
    return (
      <group rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.022, ROOM_CENTER_Z]}>
        <mesh><ringGeometry args={[2.4 * ARENA_SIZE_MULTIPLIER, 2.5 * ARENA_SIZE_MULTIPLIER, 48]} /><meshBasicMaterial color={room.accent} transparent opacity={0.32} /></mesh>
        <mesh><ringGeometry args={[5.2 * ARENA_SIZE_MULTIPLIER, 5.3 * ARENA_SIZE_MULTIPLIER, 48]} /><meshBasicMaterial color={room.accentSecondary} transparent opacity={0.22} /></mesh>
        <mesh><ringGeometry args={[7.5 * ARENA_SIZE_MULTIPLIER, 7.58 * ARENA_SIZE_MULTIPLIER, 48]} /><meshBasicMaterial color={room.accent} transparent opacity={0.16} /></mesh>
      </group>
    );
  }

  if (pattern === "pools") {
    return (
      <group rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.024, ROOM_CENTER_Z]}>
        {[[0, 0, 2.5], [4, -2.4, 1.5], [-4.4, 2.8, 1.25]].map(([x, z, radius], index) => (
          <mesh key={index} position={[x * ARENA_SIZE_MULTIPLIER, z * ARENA_SIZE_MULTIPLIER, 0]}>
            <circleGeometry args={[radius * ARENA_SIZE_MULTIPLIER, 32]} />
            <meshBasicMaterial color={index === 0 ? room.accent : room.accentSecondary} transparent opacity={0.2} depthWrite={false} />
          </mesh>
        ))}
      </group>
    );
  }

  return (
    <instancedMesh ref={rays} args={[undefined, undefined, 8]}>
      <boxGeometry args={[0.15, 0.025, 1]} />
      <meshBasicMaterial color={room.accent} transparent opacity={0.3} depthWrite={false} />
    </instancedMesh>
  );
}

function RoomScenery({ room }: { room: BossRoomDefinition }) {
  const instances = useRef<THREE.InstancedMesh>(null);
  const kind = ROOM_SCENERY[room.floor];
  const height = kind === "tree" ? 2.1 : kind === "obelisk" || kind === "banner" ? 1.5 : kind === "ice" || kind === "lightning" ? 1.2 : 0.7;

  useLayoutEffect(() => {
    if (!instances.current) return;
    const dummy = new THREE.Object3D();
    SCENERY_SLOTS.forEach(({ angle, x, z }, index) => {
      const scale = 0.78 + (index % 3) * 0.12;
      dummy.position.set(x, height * scale, z);
      dummy.rotation.set(0, -angle + (index % 2 ? 0.35 : 0), 0);
      dummy.scale.setScalar(scale);
      dummy.updateMatrix();
      instances.current!.setMatrixAt(index, dummy.matrix);
      instances.current!.setColorAt(index, new THREE.Color(index % 2 ? room.accent : room.accentSecondary));
    });
    instances.current.instanceMatrix.needsUpdate = true;
    if (instances.current.instanceColor) instances.current.instanceColor.needsUpdate = true;
  }, [height, kind, room.accent, room.accentSecondary]);

  return (
    <instancedMesh ref={instances} args={[undefined, undefined, SCENERY_SLOTS.length]} castShadow receiveShadow>
      {kind === "crystal" || kind === "shard" || kind === "ice" || kind === "lightning" ? <coneGeometry args={[0.46, height * 2, kind === "ice" ? 4 : 5]} />
        : kind === "obelisk" || kind === "grave" || kind === "moonstone" ? <boxGeometry args={[0.62, height * 2, 0.38]} />
          : kind === "coral" || kind === "reed" ? <cylinderGeometry args={[0.12, 0.34, height * 2, 5]} />
            : kind === "crown" ? <cylinderGeometry args={[0.52, 0.68, height * 2, 6]} />
              : kind === "tree" ? <coneGeometry args={[0.8, height * 2, 7]} />
                : kind === "rift" ? <tetrahedronGeometry args={[height]} />
                  : kind === "boulder" || kind === "cloud" ? <dodecahedronGeometry args={[height, 0]} />
                    : kind === "scrap" ? <boxGeometry args={[0.94, height, 0.48]} />
                      : kind === "web" ? <icosahedronGeometry args={[height, 1]} />
                        : <boxGeometry args={[0.16, height * 2, 0.95]} />}
      <meshStandardMaterial vertexColors color={room.accent} emissive={room.accent} emissiveIntensity={kind === "web" || kind === "rift" ? 0.24 : 0.08} roughness={0.68} />
    </instancedMesh>
  );
}

function RoomFloor({ room, portalOpenings = false }: { room: BossRoomDefinition; portalOpenings?: boolean }) {
  return (
    <group>
      <mesh position={[0, -0.45, ROOM_CENTER_Z]} receiveShadow>
        <cylinderGeometry args={[10.5 * ARENA_SIZE_MULTIPLIER, 11.2 * ARENA_SIZE_MULTIPLIER, 0.8, 48]} />
        <meshStandardMaterial color={room.ground} roughness={0.94} />
      </mesh>
      <mesh position={[0, -0.02, ROOM_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <circleGeometry args={[9.35 * ARENA_SIZE_MULTIPLIER, 48]} />
        <meshStandardMaterial color={room.floorColor} roughness={0.86} metalness={room.floor === "royal" || room.floor === "junkyard" ? 0.34 : 0.04} />
      </mesh>
      <mesh position={[0, -0.008, ROOM_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]}>
        <ringGeometry args={[8.62 * ARENA_SIZE_MULTIPLIER, 8.82 * ARENA_SIZE_MULTIPLIER, 48]} />
        <meshBasicMaterial color={room.accentSecondary} transparent opacity={0.32} />
      </mesh>
      <RoomMarks room={room} />
      <RoomScenery room={room} />
      <RoomWalls room={room} portalOpenings={portalOpenings} />
    </group>
  );
}

function HockeyGoal({ z, color }: { z: number; color: string }) {
  const goalWidth = HOCKEY_GOAL_HALF_WIDTH * 2;
  return (
    <group position={[0, 0, z]}>
      <mesh position={[-HOCKEY_GOAL_HALF_WIDTH, 1.05, 0]} castShadow>
        <boxGeometry args={[0.18, 2.1, 0.24]} />
        <meshStandardMaterial color={color} emissive={color} emissiveIntensity={1.7} metalness={0.45} roughness={0.32} />
      </mesh>
      <mesh position={[HOCKEY_GOAL_HALF_WIDTH, 1.05, 0]} castShadow>
        <boxGeometry args={[0.18, 2.1, 0.24]} />
        <meshStandardMaterial color={color} emissive={color} emissiveIntensity={1.7} metalness={0.45} roughness={0.32} />
      </mesh>
      <mesh position={[0, 2.03, 0]} castShadow>
        <boxGeometry args={[goalWidth, 0.16, 0.22]} />
        <meshStandardMaterial color={color} emissive={color} emissiveIntensity={1.2} metalness={0.45} roughness={0.32} />
      </mesh>
      <mesh position={[0, 1.05, z === HOCKEY_HEALER_GOAL_Z ? 0.08 : -0.08]}>
        <planeGeometry args={[goalWidth, 2]} />
        <meshBasicMaterial color={color} transparent opacity={0.14} wireframe depthWrite={false} />
      </mesh>
      <pointLight color={color} intensity={2.4} distance={7} position={[0, 1.3, 0]} />
    </group>
  );
}

function HockeyHealingRoom() {
  const goalSideWidth = (HOCKEY_ARENA_WIDTH - HOCKEY_GOAL_HALF_WIDTH * 2) * 0.5;
  const leftGoalSideX = HOCKEY_ARENA_MIN_X + goalSideWidth * 0.5;
  const rightGoalSideX = HOCKEY_ARENA_MAX_X - goalSideWidth * 0.5;
  const wallMaterial = <meshStandardMaterial color="#172d36" roughness={0.58} metalness={0.48} />;
  return (
    <group>
      <color attach="background" args={["#020b11"]} />
      <fog attach="fog" args={["#061a24", 18, 46]} />
      <hemisphereLight args={["#80dfff", "#061015", 1.08]} />
      <directionalLight castShadow position={[6, 12, 8]} intensity={1.8} color="#d6f7ff" shadow-mapSize={[512, 512]} />
      <pointLight color="#50dfff" intensity={2.2} distance={14} position={[0, 4, HOCKEY_MIDLINE_Z]} />
      <mesh position={[0, -0.42, HOCKEY_ARENA_CENTER_Z]} receiveShadow>
        <boxGeometry args={[HOCKEY_ARENA_WIDTH + 1.2, 0.8, HOCKEY_ARENA_LENGTH + 1.2]} />
        <meshStandardMaterial color="#061117" roughness={0.9} />
      </mesh>
      <mesh position={[0, -0.01, HOCKEY_ARENA_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <planeGeometry args={[HOCKEY_ARENA_WIDTH, HOCKEY_ARENA_LENGTH]} />
        <meshStandardMaterial color="#102731" roughness={0.68} metalness={0.3} />
      </mesh>
      <mesh position={[0, 0.012, HOCKEY_MIDLINE_Z]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[HOCKEY_ARENA_WIDTH - 0.5, 0.09]} />
        <meshBasicMaterial color="#64e7ff" transparent opacity={0.62} />
      </mesh>
      <mesh position={[0, 0.018, HOCKEY_MIDLINE_Z]} rotation={[-Math.PI / 2, 0, 0]}>
        <ringGeometry args={[2.45, 2.55, 48]} />
        <meshBasicMaterial color="#64e7ff" transparent opacity={0.44} />
      </mesh>
      <mesh position={[HOCKEY_ARENA_MIN_X - 0.15, 1.5, HOCKEY_ARENA_CENTER_Z]} castShadow receiveShadow>
        <boxGeometry args={[0.3, 3, HOCKEY_ARENA_LENGTH + 0.6]} />
        {wallMaterial}
      </mesh>
      <mesh position={[HOCKEY_ARENA_MAX_X + 0.15, 1.5, HOCKEY_ARENA_CENTER_Z]} castShadow receiveShadow>
        <boxGeometry args={[0.3, 3, HOCKEY_ARENA_LENGTH + 0.6]} />
        {wallMaterial}
      </mesh>
      {[HOCKEY_ARENA_MIN_Z - 0.15, HOCKEY_ARENA_MAX_Z + 0.15].flatMap((z) => [leftGoalSideX, rightGoalSideX].map((x) => (
        <mesh key={`${x}-${z}`} position={[x, 1.5, z]} castShadow receiveShadow>
          <boxGeometry args={[goalSideWidth, 3, 0.3]} />
          <meshStandardMaterial color="#172d36" roughness={0.58} metalness={0.48} />
        </mesh>
      )))}
      <HockeyGoal z={HOCKEY_NPC_GOAL_Z} color="#ff6e5c" />
      <HockeyGoal z={HOCKEY_HEALER_GOAL_Z} color="#67e8ff" />
    </group>
  );
}

const BLOCKBREAKER_BIOME_FIXTURES = [
  [-12.75, -12.5, 0.94],
  [-12.75, -1, 1.08],
  [-12.75, 10.5, 0.9],
  [12.75, -12.5, 1.02],
  [12.75, -1, 0.88],
  [12.75, 10.5, 1.12],
] as const;

function BlockbreakerFixtureGeometry({ fixture }: { fixture: BlockbreakerBiomeFixture }) {
  if (fixture === "crystal") return <octahedronGeometry args={[1.25, 0]} />;
  if (fixture === "forge") return <cylinderGeometry args={[0.72, 1.18, 2.8, 6]} />;
  if (fixture === "spire") return <coneGeometry args={[1.05, 3.7, 5]} />;
  if (fixture === "monolith") return <boxGeometry args={[1.25, 3.5, 1.25]} />;
  return <torusGeometry args={[1.08, 0.3, 8, 18]} />;
}

function BlockbreakerBiomeFixtures({ biome }: { biome: BlockbreakerArenaBiome }) {
  const mesh = useRef<THREE.InstancedMesh>(null);
  const transform = useMemo(() => new THREE.Object3D(), []);

  useLayoutEffect(() => {
    if (!mesh.current) return;
    BLOCKBREAKER_BIOME_FIXTURES.forEach(([x, z, scale], index) => {
      transform.position.set(x, biome.fixture === "reactor" ? 1.45 : 1.75, z);
      transform.rotation.set(
        biome.fixture === "reactor" ? 0 : index % 2 === 0 ? -0.08 : 0.08,
        index * 0.73,
        biome.fixture === "reactor" ? 0 : index % 2 === 0 ? 0.06 : -0.06,
      );
      transform.scale.setScalar(scale);
      transform.updateMatrix();
      mesh.current!.setMatrixAt(index, transform.matrix);
    });
    mesh.current.instanceMatrix.needsUpdate = true;
    mesh.current.computeBoundingSphere();
  }, [biome.fixture, transform]);

  return (
    <instancedMesh ref={mesh} args={[undefined, undefined, BLOCKBREAKER_BIOME_FIXTURES.length]} castShadow receiveShadow>
      <BlockbreakerFixtureGeometry fixture={biome.fixture} />
      <meshStandardMaterial
        color={biome.fixtureColor}
        emissive={biome.fixtureEmissive}
        emissiveIntensity={0.72}
        roughness={biome.fixture === "monolith" ? 0.26 : 0.48}
        metalness={biome.fixture === "forge" || biome.fixture === "reactor" ? 0.42 : 0.12}
      />
    </instancedMesh>
  );
}

function BrightArcadeRoom({
  variant,
  biome = BLOCKBREAKER_BIOMES[0],
}: {
  variant: "blockbreaker" | "aether-assault";
  biome?: BlockbreakerArenaBiome;
}) {
  const roomWidth = HOCKEY_ARENA_WIDTH + 10;
  const roomLength = HOCKEY_ARENA_LENGTH + 12;
  const roomMinX = HOCKEY_ARENA_MIN_X - 5;
  const roomMaxX = HOCKEY_ARENA_MAX_X + 5;
  const roomMinZ = HOCKEY_ARENA_MIN_Z - 6;
  const roomMaxZ = HOCKEY_ARENA_MAX_Z + 6;
  const wallHeight = 4.8;
  const wallMaterial = (
    <meshStandardMaterial
      color={biome.wall}
      emissive={biome.wallEmissive}
      emissiveIntensity={biome.wallEmissiveIntensity}
      roughness={0.62}
      metalness={0.12}
    />
  );

  return (
    <group key={variant === "blockbreaker" ? biome.id : variant}>
      <color attach="background" args={[biome.background]} />
      <fog attach="fog" args={[biome.fog, 34, 72]} />
      <ambientLight color={biome.ambient} intensity={biome.ambientIntensity} />
      <hemisphereLight args={[biome.sky, biome.ground, biome.hemisphereIntensity]} />
      <directionalLight
        castShadow
        position={[8, 15, 10]}
        intensity={biome.keyLightIntensity}
        color={biome.keyLight}
        shadow-mapSize={[512, 512]}
      />
      <pointLight color={biome.fillLightA} intensity={biome.fillLightIntensityA} distance={30} position={[-8, 7, 7]} />
      <pointLight color={biome.fillLightB} intensity={biome.fillLightIntensityB} distance={30} position={[8, 7, -9]} />

      <mesh position={[0, -0.48, HOCKEY_ARENA_CENTER_Z]} receiveShadow>
        <boxGeometry args={[roomWidth + 1.2, 0.9, roomLength + 1.2]} />
        <meshStandardMaterial color={biome.foundation} roughness={0.92} metalness={0.04} />
      </mesh>
      <mesh position={[0, -0.015, HOCKEY_ARENA_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <planeGeometry args={[roomWidth, roomLength]} />
        <meshStandardMaterial color={biome.floor} roughness={biome.floorRoughness} metalness={biome.floorMetalness} />
      </mesh>

      <mesh position={[0, 0.006, HOCKEY_ARENA_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[HOCKEY_ARENA_WIDTH, HOCKEY_ARENA_LENGTH]} />
        <meshStandardMaterial color={biome.playfield} transparent opacity={0.72} roughness={0.52} metalness={0.05} />
      </mesh>
      {[HOCKEY_ARENA_MIN_X, HOCKEY_ARENA_MAX_X].map((x) => (
        <mesh key={`lane-x-${x}`} position={[x, 0.025, HOCKEY_ARENA_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]}>
          <planeGeometry args={[0.1, HOCKEY_ARENA_LENGTH]} />
          <meshBasicMaterial color={biome.boundary} transparent opacity={0.72} toneMapped={false} />
        </mesh>
      ))}
      {[HOCKEY_ARENA_MIN_Z, HOCKEY_ARENA_MAX_Z].map((z) => (
        <mesh key={`lane-z-${z}`} position={[0, 0.025, z]} rotation={[-Math.PI / 2, 0, 0]}>
          <planeGeometry args={[HOCKEY_ARENA_WIDTH, 0.1]} />
          <meshBasicMaterial color={biome.boundary} transparent opacity={0.72} toneMapped={false} />
        </mesh>
      ))}
      {variant === "blockbreaker" ? (
        <mesh position={[0, 0.028, HOCKEY_MIDLINE_Z]} rotation={[-Math.PI / 2, 0, 0]}>
          <planeGeometry args={[HOCKEY_ARENA_WIDTH, 0.12]} />
          <meshBasicMaterial color={biome.midline} transparent opacity={0.86} toneMapped={false} />
        </mesh>
      ) : (
        <group>
          {[-6.7, -3.35, 0, 3.35, 6.7].map((x) => (
            <mesh key={`aether-lane-${x}`} position={[x, 0.029, HOCKEY_ARENA_CENTER_Z]} rotation={[-Math.PI / 2, 0, 0]}>
              <planeGeometry args={[0.055, HOCKEY_ARENA_LENGTH - 0.8]} />
              <meshBasicMaterial color="#c6fbff" transparent opacity={x === 0 ? 0.58 : 0.3} toneMapped={false} />
            </mesh>
          ))}
          {[-10.7, -8.45, -6.2, -3.95].map((z) => (
            <mesh key={`aether-rank-${z}`} position={[0, 0.03, z]} rotation={[-Math.PI / 2, 0, 0]}>
              <planeGeometry args={[HOCKEY_ARENA_WIDTH - 1, 0.045]} />
              <meshBasicMaterial color="#fff2bf" transparent opacity={0.36} toneMapped={false} />
            </mesh>
          ))}
        </group>
      )}

      {[roomMinX - 0.18, roomMaxX + 0.18].map((x) => (
        <mesh key={`room-x-${x}`} position={[x, wallHeight * 0.5, HOCKEY_ARENA_CENTER_Z]} castShadow receiveShadow>
          <boxGeometry args={[0.36, wallHeight, roomLength + 0.7]} />
          {wallMaterial}
        </mesh>
      ))}
      {[roomMinZ - 0.18, roomMaxZ + 0.18].map((z) => (
        <mesh key={`room-z-${z}`} position={[0, wallHeight * 0.5, z]} castShadow receiveShadow>
          <boxGeometry args={[roomWidth + 0.7, wallHeight, 0.36]} />
          {wallMaterial}
        </mesh>
      ))}
      {[roomMinX - 0.36, roomMaxX + 0.36].map((x) => (
        <mesh key={`light-x-${x}`} position={[x, 3.7, HOCKEY_ARENA_CENTER_Z]}>
          <boxGeometry args={[0.12, 0.18, roomLength - 1]} />
          <meshBasicMaterial color={biome.railA} toneMapped={false} />
        </mesh>
      ))}
      {[roomMinZ - 0.36, roomMaxZ + 0.36].map((z) => (
        <mesh key={`light-z-${z}`} position={[0, 3.7, z]}>
          <boxGeometry args={[roomWidth - 1, 0.18, 0.12]} />
          <meshBasicMaterial color={biome.railB} toneMapped={false} />
        </mesh>
      ))}
      {variant === "blockbreaker" && <BlockbreakerBiomeFixtures biome={biome} />}
    </group>
  );
}

function HealingHockeyPvpRoom() {
  const width = HOCKEY_PVP_ARENA_MAX_X - HOCKEY_PVP_ARENA_MIN_X;
  const length = HOCKEY_PVP_ARENA_MAX_Z - HOCKEY_PVP_ARENA_MIN_Z;
  const goalSideWidth = (width - HOCKEY_PVP_GOAL_HALF_WIDTH * 2) * 0.5;
  const sideCenters = [
    HOCKEY_PVP_ARENA_MIN_X + goalSideWidth * 0.5,
    HOCKEY_PVP_ARENA_MAX_X - goalSideWidth * 0.5,
  ];
  return (
    <group>
      <color attach="background" args={["#040912"]} />
      <fog attach="fog" args={["#071522", 28, 68]} />
      <hemisphereLight args={["#8deaff", "#130812", 1.1]} />
      <directionalLight castShadow position={[7, 14, 10]} intensity={1.9} color="#d8fbff" shadow-mapSize={[512, 512]} />
      <pointLight color="#62e7ff" intensity={2.5} distance={18} position={[0, 4, 13]} />
      <pointLight color="#ff6f83" intensity={2.5} distance={18} position={[0, 4, -13]} />
      <mesh position={[0, -0.42, 0]} receiveShadow>
        <boxGeometry args={[width + 1.2, 0.8, length + 1.2]} />
        <meshStandardMaterial color="#070d17" roughness={0.9} />
      </mesh>
      <mesh position={[0, -0.01, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <planeGeometry args={[width, length]} />
        <meshStandardMaterial color="#112632" roughness={0.7} metalness={0.32} />
      </mesh>
      <mesh position={[0, 0.012, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[width - 0.5, 0.11]} />
        <meshBasicMaterial color="#f4d978" transparent opacity={0.7} />
      </mesh>
      {[-11, 11].map((z) => <mesh key={z} position={[0, 0.014, z]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[width - 0.8, 0.07]} />
        <meshBasicMaterial color={z > 0 ? "#64e7ff" : "#ff7688"} transparent opacity={0.38} />
      </mesh>)}
      {[HOCKEY_PVP_ARENA_MIN_X - 0.15, HOCKEY_PVP_ARENA_MAX_X + 0.15].map((x) => (
        <mesh key={x} position={[x, 1.5, 0]} castShadow receiveShadow>
          <boxGeometry args={[0.3, 3, length + 0.6]} />
          <meshStandardMaterial color="#172b38" roughness={0.58} metalness={0.48} />
        </mesh>
      ))}
      {[-HOCKEY_PVP_ARENA_MAX_Z - 0.15, HOCKEY_PVP_ARENA_MAX_Z + 0.15].flatMap((z) => sideCenters.map((x) => (
        <mesh key={`${x}-${z}`} position={[x, 1.5, z]} castShadow receiveShadow>
          <boxGeometry args={[goalSideWidth, 3, 0.3]} />
          <meshStandardMaterial color="#172b38" roughness={0.58} metalness={0.48} />
        </mesh>
      )))}
      <HockeyGoal z={-HOCKEY_PVP_GOAL_Z} color="#ff647b" />
      <HockeyGoal z={HOCKEY_PVP_GOAL_Z} color="#62e7ff" />
      <HockeyPvpScoreboards />
    </group>
  );
}

function HockeyPvpScoreboards() {
  const localGoals = useGameStore((state) => state.hockeyPvp.opponentGoalsConceded);
  const opponentGoals = useGameStore((state) => state.hockeyPvp.localGoalsConceded);
  const texture = useMemo(() => {
    const canvas = document.createElement("canvas");
    canvas.width = 512;
    canvas.height = 192;
    const next = new THREE.CanvasTexture(canvas);
    next.colorSpace = THREE.SRGBColorSpace;
    next.minFilter = THREE.LinearMipmapLinearFilter;
    next.magFilter = THREE.LinearFilter;
    return next;
  }, []);

  useEffect(() => {
    const canvas = texture.image as HTMLCanvasElement;
    const context = canvas.getContext("2d");
    if (!context) return;
    const gradient = context.createLinearGradient(0, 0, canvas.width, canvas.height);
    gradient.addColorStop(0, "#061d28");
    gradient.addColorStop(0.5, "#05080d");
    gradient.addColorStop(1, "#2b0a17");
    context.fillStyle = gradient;
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.strokeStyle = "#89efff";
    context.lineWidth = 5;
    context.strokeRect(5, 5, canvas.width - 10, canvas.height - 10);
    context.fillStyle = "#a8c4c9";
    context.font = "700 20px Inter, sans-serif";
    context.textAlign = "center";
    context.fillText("HEALING HOCKEY", canvas.width / 2, 31);
    context.fillStyle = "#74ecff";
    context.font = "700 18px Inter, sans-serif";
    context.fillText("YOU", 132, 58);
    context.fillStyle = "#ff819f";
    context.fillText("RIVAL", 380, 58);
    context.font = "700 94px Impact, Inter, sans-serif";
    context.fillStyle = "#eaffff";
    context.fillText(String(Math.min(99, localGoals)).padStart(2, "0"), 132, 151);
    context.fillStyle = "#ffedf4";
    context.fillText(String(Math.min(99, opponentGoals)).padStart(2, "0"), 380, 151);
    context.fillStyle = "#f3d87c";
    context.font = "700 56px Inter, sans-serif";
    context.fillText("–", 256, 137);
    texture.needsUpdate = true;
  }, [localGoals, opponentGoals, texture]);

  useEffect(() => () => texture.dispose(), [texture]);

  return <>{([-1, 1] as const).map((side) => (
    <group
      key={side}
      position={[side * 9.78, 4.05, 5.5]}
      rotation={[0, side < 0 ? Math.PI / 2 : -Math.PI / 2, 0]}
    >
      <mesh castShadow>
        <boxGeometry args={[7.6, 3.25, 0.24]} />
        <meshStandardMaterial color="#101923" roughness={0.32} metalness={0.72} />
      </mesh>
      <mesh position={[0, 0, 0.126]}>
        <planeGeometry args={[7.28, 2.93]} />
        <meshBasicMaterial map={texture} toneMapped={false} />
      </mesh>
    </group>
  ))}</>;
}

/** Main-display room projection. Gameplay stays in the shared arena domain. */
export function BossRoom() {
  const bossId = useGameStore((state) => state.boss.id);
  const blockbreakerSeed = useGameStore((state) => state.blockbreaker.seed);
  const hockeyMode = useGameStore((state) => state.activityMode === "hockey-healing");
  const blockbreakerMode = useGameStore((state) => state.activityMode === "blockbreaker");
  const aetherAssaultMode = useGameStore((state) => state.activityMode === "aether-assault");
  const hockeyPvpMode = useGameStore((state) => state.activityMode === "hockey-healing-pvp");
  const rpgPhase = useGameStore((state) => state.rpgRun?.phase ?? null);
  const rpgBossRoom = useGameStore((state) => state.runMode === "rpg-roguelike" && state.activityMode === "boss");
  if (hockeyPvpMode) return <HealingHockeyPvpRoom />;
  if (aetherAssaultMode) return <BrightArcadeRoom variant="aether-assault" />;
  if (blockbreakerMode) return <BrightArcadeRoom variant="blockbreaker" biome={blockbreakerBiomeForSeed(blockbreakerSeed)} />;
  if (hockeyMode) return <HockeyHealingRoom />;
  const room = bossRoomFor(bossId);
  return (
    <group key={room.id}>
      <color attach="background" args={[room.background]} />
      <fog attach="fog" args={[room.fog, 15 * ARENA_SIZE_MULTIPLIER, 32 * ARENA_SIZE_MULTIPLIER]} />
      <hemisphereLight args={[room.sky, room.ground, 1.18]} />
      <directionalLight castShadow position={[5, 10, 8]} intensity={2.1} color={room.accentSecondary} shadow-mapSize={[512, 512]} />
      <pointLight color={room.accent} intensity={2.35} distance={8} position={[-6, 2.8, ROOM_CENTER_Z]} />
      <pointLight color={room.accentSecondary} intensity={1.75} distance={7} position={[6, 2.4, ROOM_CENTER_Z - 1]} />
      <RoomFloor room={room} portalOpenings={rpgBossRoom} />
      {rpgBossRoom && rpgPhase && (
        <RpgRoomPortals
          entryOpen
          exitOpen={rpgPhase === "boss-cleared" || rpgPhase === "reward"}
          accent={room.accent}
          wallColor={room.wallColor}
        />
      )}
    </group>
  );
}

if (LEGACY_GAME_ASSETS_FORCED) {
  useGLTF.preload(KAYKIT_DUNGEON_PILLAR_URL, false, true);
  useGLTF.preload(KAYKIT_DUNGEON_WALL_URL, false, true);
  useGLTF.preload(KAYKIT_DUNGEON_TORCH_URL, false, true);
}
