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 { bossRoomFor, type BossRoomDefinition, type BossRoomFloor } from "../game/bossRooms";
import { useGameStore } from "../game/store";
import { LEGACY_GAME_ASSETS_FORCED, useGameGLTF } from "./GameAssetProvider";

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) {
  return ARENA_WALL_SEGMENTS.map((fixture) => ({
    ...fixture,
    scaleY: room.wallHeight / 4,
  }));
}

function LegacyArenaArchitecture({ room }: { room: BossRoomDefinition }) {
  const walls = useMemo(() => arenaWalls(room), [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 }: { room: BossRoomDefinition }) {
  const gltf = useGameGLTF(KAYKIT_DUNGEON_KIT_URL);
  const walls = useMemo(() => arenaWalls(room), [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 }: { room: BossRoomDefinition }) {
  const walls = useRef<THREE.Group>(null);
  const previousCameraPosition = useRef<THREE.Vector3 | null>(null);

  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}>
      {ARENA_WALL_SEGMENTS.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 }: { room: BossRoomDefinition }) {
  return (
    <Suspense fallback={<RoomWallFallback room={room} />}>
      <LegacyArenaArchitecture room={room} />
    </Suspense>
  );
}

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

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

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 }: { room: BossRoomDefinition }) {
  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} />
    </group>
  );
}

/** Main-display room projection. Gameplay stays in the shared arena domain. */
export function BossRoom() {
  const bossId = useGameStore((state) => state.boss.id);
  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} />
    </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);
}
