/**
 * Probe component - renders space probes with 3D models
 */
import { useRef, useMemo, useState, useEffect } from 'react';
import { Group } from 'three';
import * as THREE from 'three';
import { useGLTF, Html } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import type { CelestialBody } from '../types';
import { calculateRenderPosition, getOffsetDescription } from '../utils/renderPosition';
import { fetchBodyResources } from '../utils/api';

interface ProbeProps {
  body: CelestialBody;
  allBodies: CelestialBody[];
}

// Separate component for each probe type to properly use hooks
function ProbeModel({ body, modelPath, allBodies, onError }: {
  body: CelestialBody;
  modelPath: string;
  allBodies: CelestialBody[];
  onError: () => void;
}) {
  const groupRef = useRef<Group>(null);
  const position = body.positions[0];

  // 1. Hook: Render Position
  const renderPosition = useMemo(() => {
    return calculateRenderPosition(body, allBodies);
  }, [position.x, position.y, position.z, body, allBodies]);

  const scaledPos = { x: renderPosition.x, y: renderPosition.y, z: renderPosition.z };

  // 2. Hook: Load GLTF
  // We removed the try-catch block because calling hooks conditionally or inside try-catch is forbidden.
  // If useGLTF fails, it will throw an error which should be caught by an ErrorBoundary or handled by Suspense.
  // Since we preload in the parent, this should generally be safe.
  const gltf = useGLTF(modelPath);
  const scene = gltf.scene;

  // 3. Hook: Optimal Scale
  const optimalScale = useMemo(() => {
    if (!scene) return 1;

    // Calculate bounding box to determine model size
    const box = new THREE.Box3().setFromObject(scene);
    const size = new THREE.Vector3();
    box.getSize(size);

    // Get the largest dimension
    const maxDimension = Math.max(size.x, size.y, size.z);

    // Target size for display (consistent visual size)
    const targetSize = 0.5; // Target visual size in scene units

    // Calculate scale factor
    const calculatedScale = maxDimension > 0 ? targetSize / maxDimension : 0.2;

    // Clamp scale to reasonable range
    const finalScale = Math.max(0.1, Math.min(2.0, calculatedScale));

    return finalScale;
  }, [scene, body.name]);

  // 4. Hook: Configured Scene
  const configuredScene = useMemo(() => {
    if (!scene) return null;

    const clonedScene = scene.clone();
    clonedScene.traverse((child: any) => {
      if (child.isMesh) {
        child.renderOrder = 10000;
        if (child.material) {
          if (Array.isArray(child.material)) {
            child.material = child.material.map((mat: any) => {
              const clonedMat = mat.clone();
              clonedMat.depthTest = true;
              clonedMat.depthWrite = true;
              clonedMat.transparent = false;
              clonedMat.opacity = 1.0;
              clonedMat.alphaTest = 0;
              clonedMat.needsUpdate = true;
              return clonedMat;
            });
          } else {
            child.material = child.material.clone();
            child.material.depthTest = true;
            child.material.depthWrite = true;
            child.material.transparent = false;
            child.material.opacity = 1.0;
            child.material.alphaTest = 0;
            child.material.needsUpdate = true;
          }
        }
      }
    });
    return clonedScene;
  }, [scene]);

  // 5. Hook: Animation
  useFrame((_, delta) => {
    if (groupRef.current) {
      groupRef.current.rotation.y += delta * 0.2;
    }
  });

  // Render Logic
  if (!scene || !configuredScene) return null;

  // Calculate ACTUAL distance from Sun (not scaled)
  const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2);

  // Get offset description if this probe has one
  const offsetDesc = renderPosition.hasOffset ? getOffsetDescription(body, allBodies) : null;

  return (
    <group position={[scaledPos.x, scaledPos.z, scaledPos.y]} ref={groupRef}>
      <primitive
        object={configuredScene}
        scale={optimalScale}
      />
      
      {/* Name label */}
      <Html
        position={[0, optimalScale * 2, 0]}
        center
        distanceFactor={15}
        style={{
          color: '#00ffff',
          fontSize: '12px',
          fontWeight: 'bold',
          textShadow: '0 0 6px rgba(0,255,255,0.8)',
          pointerEvents: 'none',
          userSelect: 'none',
          whiteSpace: 'nowrap',
        }}
      >
        🛰️ {body.name_zh || body.name}
        {offsetDesc && (
          <>
            <br />
            <span style={{ fontSize: '10px', color: '#ffaa00', opacity: 0.9 }}>
              {offsetDesc}
            </span>
          </>
        )}
        <br />
        <span style={{ fontSize: '10px', opacity: 0.8 }}>
          {distance.toFixed(2)} AU
        </span>
      </Html>
    </group>
  );
}

// Fallback component when model is not available
function ProbeFallback({ body, allBodies }: { body: CelestialBody; allBodies: CelestialBody[] }) {
  const position = body.positions[0];

  // Use smart render position calculation
  const renderPosition = useMemo(() => {
    return calculateRenderPosition(body, allBodies);
  }, [position.x, position.y, position.z, body, allBodies]);

  const scaledPos = { x: renderPosition.x, y: renderPosition.y, z: renderPosition.z };

  // Calculate ACTUAL distance from Sun (not scaled)
  const distance = Math.sqrt(position.x ** 2 + position.y ** 2 + position.z ** 2);

  // Get offset description if this probe has one
  const offsetDesc = renderPosition.hasOffset ? getOffsetDescription(body, allBodies) : null;

  return (
    <group position={[scaledPos.x, scaledPos.z, scaledPos.y]}>
      <mesh>
        <sphereGeometry args={[0.15, 16, 16]} />
        <meshStandardMaterial color="#ff0000" emissive="#ff0000" emissiveIntensity={0.8} />
      </mesh>

      {/* Name label */}
      <Html
        position={[0, 1, 0]}
        center
        distanceFactor={15}
        style={{
          color: '#ff6666',
          fontSize: '12px',
          fontWeight: 'bold',
          textShadow: '0 0 6px rgba(255,0,0,0.8)',
          pointerEvents: 'none',
          userSelect: 'none',
          whiteSpace: 'nowrap',
        }}
      >
        🛰️ {body.name_zh || body.name}
        {offsetDesc && (
          <>
            <br />
            <span style={{ fontSize: '10px', color: '#ffaa00', opacity: 0.9 }}>
              {offsetDesc}
            </span>
          </>
        )}
        <br />
        <span style={{ fontSize: '10px', opacity: 0.8 }}>
          {distance.toFixed(2)} AU
        </span>
      </Html>
    </group>
  );
}

export function Probe({ body, allBodies }: ProbeProps) {
  const position = body.positions[0];
  const [modelPath, setModelPath] = useState<string | null | undefined>(undefined);
  const [loadError, setLoadError] = useState<boolean>(false);

  // Fetch model from backend API
  useEffect(() => {
    console.log(`[Probe ${body.name}] Fetching resources...`);
    setLoadError(false); // Reset error state

    fetchBodyResources(body.id, 'model')
      .then((response) => {
        console.log(`[Probe ${body.name}] Resources response:`, response);
        if (response.resources.length > 0) {
          // Get the first model resource
          const modelResource = response.resources[0];
          // Construct full URL from file_path
          const protocol = window.location.protocol;
          const hostname = window.location.hostname;
          const port = import.meta.env.VITE_API_BASE_URL ? '' : ':8000';
          const fullPath = `${protocol}//${hostname}${port}/upload/${modelResource.file_path}`;
          console.log(`[Probe ${body.name}] Model path:`, fullPath);

          // Preload the model before setting the path
          useGLTF.preload(fullPath);
          console.log(`[Probe ${body.name}] Model preloaded`);

          setModelPath(fullPath);
        } else {
          console.log(`[Probe ${body.name}] No resources found, using fallback`);
          setModelPath(null);
        }
      })
      .catch((err) => {
        console.error(`[Probe ${body.name}] Failed to load model:`, err);
        setLoadError(true);
        setModelPath(null);
      });
  }, [body.id, body.name]);

  if (!position) {
    console.log(`[Probe ${body.name}] No position data`);
    return null;
  }

  if (modelPath === undefined) {
    console.log(`[Probe ${body.name}] Waiting for model path...`);
    return null; // Wait for model to load
  }

  console.log(`[Probe ${body.name}] Rendering with modelPath:`, modelPath, 'loadError:', loadError);

  // Use model if available and no load error, otherwise use fallback
  if (modelPath && !loadError) {
    return <ProbeModel body={body} modelPath={modelPath} allBodies={allBodies} onError={() => {
      console.error(`[Probe ${body.name}] ProbeModel rendering failed, switching to fallback`);
      setLoadError(true);
    }} />;
  }

  return <ProbeFallback body={body} allBodies={allBodies} />;
}
