import { useEffect, useMemo, useState, useRef } from "react";
import { useGLTF } from "@react-three/drei";
import * as THREE from "three";
import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode";
import { loadMapSceneData } from "@/utils/loadMapSceneData";
import type { OctreeReadyHandler } from "@/types/three";
import type { MapNode } from "@/types/editor";

interface LoadedMapNode {
  node: MapNode;
  modelUrl: string;
}

interface GameMapProps {
  onOctreeReady: OctreeReadyHandler;
}

export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element {
  const [mapNodes, setMapNodes] = useState<LoadedMapNode[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const groupRef = useRef<THREE.Group>(null);

  useOctreeGraphNode(groupRef, onOctreeReady, mapNodes.length);

  useEffect(() => {
    const loadMap = async () => {
      try {
        const sceneData = await loadMapSceneData();
        if (!sceneData) {
          console.warn("map.json not found");
          setIsLoading(false);
          return;
        }

        const loadedMapNodes = sceneData.mapNodes.flatMap((node) => {
          const modelUrl = sceneData.models.get(node.name);
          return modelUrl ? [{ node, modelUrl }] : [];
        });
        const missingModelCount =
          sceneData.mapNodes.length - loadedMapNodes.length;

        if (missingModelCount > 0) {
          console.warn(
            `${missingModelCount} map nodes were skipped because their model files are missing.`,
          );
        }

        setMapNodes(loadedMapNodes);
      } catch (error) {
        console.error("Error loading map:", error);
      } finally {
        setIsLoading(false);
      }
    };

    loadMap();
  }, []);

  return (
    <group ref={groupRef}>
      {!isLoading &&
        mapNodes.map((node, index) => (
          <ModelInstance
            key={index}
            node={node.node}
            modelUrl={node.modelUrl}
          />
        ))}
    </group>
  );
}

function ModelInstance({
  node,
  modelUrl,
}: {
  node: MapNode;
  modelUrl: string;
}): React.JSX.Element {
  const groupRef = useRef<THREE.Group>(null);
  const { scene } = useGLTF(modelUrl);
  const sceneInstance = useMemo(() => scene.clone(true), [scene]);
  const { position, rotation, scale } = node;

  useEffect(() => {
    if (groupRef.current) {
      groupRef.current.position.set(...position);
      groupRef.current.rotation.set(...rotation);
      groupRef.current.scale.set(...scale);
    }
  }, [position, rotation, scale]);

  return (
    <primitive
      ref={groupRef}
      object={sceneInstance}
      position={position}
      rotation={rotation}
      scale={scale}
    />
  );
}
