import { useState, useRef, useCallback, Suspense, useEffect, useMemo, memo } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { OrbitControls, Html } from '@react-three/drei';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
import * as THREE from 'three';
import { ChevronLeft, ChevronRight, Bug, Crosshair, Check, Copy, RotateCcw, Trash2, Lightbulb, Sparkles } from 'lucide-react';
import { useHealthStore, BODY_REGION_POSITIONS } from '../store/useHealthStore';
import { CONDITION_COLORS } from '../utils/constants';
import { cn, formatDate, getSeverityColor } from '../utils/helpers';
import { fetchModelWithCache } from '../utils/modelCache';
import type { Condition } from '../types/medical';

// Model transform constants (must match <primitive> props below)
const MODEL_SCALE = 0.5;

type RenderMode = 'studio' | 'xray';

// Materials are hoisted to module scope so switching renderMode just swaps a
// reference instead of allocating new GPU resources every toggle.
const STUDIO_MATERIAL = new THREE.MeshStandardMaterial({
  color: '#F5F0EA',
  roughness: 0.55,
  metalness: 0.05,
  side: THREE.DoubleSide,
});

// Fresnel-based x-ray shader. Cheap (no transmission pass) but reads as a real
// x-ray: edges glow bright cyan, interior is faint, and additive blending lets
// overlapping geometry build up density the way real x-rays do.
const XRAY_MATERIAL = new THREE.ShaderMaterial({
  uniforms: {
    uColor: { value: new THREE.Color('#a8d8ff') },
    uPower: { value: 2.2 },
    uBaseGlow: { value: 0.06 },
  },
  vertexShader: `
    varying vec3 vNormal;
    varying vec3 vViewPos;
    void main() {
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
      vViewPos = mvPosition.xyz;
      vNormal = normalize(normalMatrix * normal);
      gl_Position = projectionMatrix * mvPosition;
    }
  `,
  fragmentShader: `
    varying vec3 vNormal;
    varying vec3 vViewPos;
    uniform vec3 uColor;
    uniform float uPower;
    uniform float uBaseGlow;
    void main() {
      vec3 viewDir = normalize(-vViewPos);
      float facing = abs(dot(vNormal, viewDir));     // 1 = facing camera, 0 = silhouette
      float fresnel = pow(1.0 - facing, uPower);
      float intensity = uBaseGlow + fresnel;
      gl_FragColor = vec4(uColor * intensity, intensity);
    }
  `,
  transparent: true,
  blending: THREE.AdditiveBlending,
  side: THREE.DoubleSide,
  depthWrite: false,
});

// Human body model loaded from .obj file with caching
const HumanBodyModel = memo(function HumanBodyModel({ objPath, renderMode }: { objPath: string; renderMode: RenderMode }) {
  const [loadedObj, setLoadedObj] = useState<THREE.Group | null>(null);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let isMounted = true;

    const loadModel = async () => {
      try {
        // Fetch model with caching
        const arrayBuffer = await fetchModelWithCache(objPath);
        
        if (!isMounted) return;

        // Parse the ArrayBuffer as text
        const text = new TextDecoder().decode(arrayBuffer);
        
        // Use OBJLoader to parse the text
        const objLoader = new OBJLoader();
        const obj = objLoader.parse(text);
        
        setLoadedObj(obj);
        setError(null);
      } catch (err) {
        if (isMounted) {
          setError(err instanceof Error ? err : new Error('Failed to load model'));
        }
      }
    };

    loadModel();

    return () => {
      isMounted = false;
    };
  }, [objPath]);

  // Clone once per loaded object; switching renderMode only swaps materials, not geometry.
  const clonedObj = useMemo(() => loadedObj?.clone() || null, [loadedObj]);

  useEffect(() => {
    if (!clonedObj) return;

    const mat = renderMode === 'studio' ? STUDIO_MATERIAL : XRAY_MATERIAL;
    clonedObj.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        child.material = mat;
        child.userData.isBodyMesh = true;
      }
    });
  }, [clonedObj, renderMode]);

  if (error) {
    return (
      <Html>
        <div className="text-red-500 text-sm">Failed to load model</div>
      </Html>
    );
  }

  if (!clonedObj) {
    return null;
  }

  return (
    <primitive
      object={clonedObj}
      scale={[MODEL_SCALE, MODEL_SCALE, MODEL_SCALE]}
      position={[0, 0, 0]}
      dispose={null}
      renderOrder={renderMode === 'xray' ? -1 : 0}
    />
  );
});

// Loading fallback component
function ModelLoader() {
  return (
    <mesh>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  );
}

type RegionPoint = { x: number; y: number; z: number };
type ScaledPoint = [number, number, number];
type RegionNodeSpec =
  | { kind: 'single'; anchors: ScaledPoint[]; centroid: ScaledPoint }
  | { kind: 'bilateral'; anchors: ScaledPoint[]; centroid: ScaledPoint }
  | { kind: 'cluster'; anchors: ScaledPoint[]; centroid: ScaledPoint }
  | { kind: 'split'; anchors: ScaledPoint[]; centroid: ScaledPoint; sideAnchors: [ScaledPoint[], ScaledPoint[]]; sideCentroids: [ScaledPoint, ScaledPoint] };

function scaleRegionPoint(position: RegionPoint): ScaledPoint {
  return [position.x * MODEL_SCALE, position.y * MODEL_SCALE, position.z * MODEL_SCALE];
}

function averagePoints(points: RegionPoint[]): RegionPoint {
  const total = points.reduce(
    (sum, point) => ({
      x: sum.x + point.x,
      y: sum.y + point.y,
      z: sum.z + point.z,
    }),
    { x: 0, y: 0, z: 0 },
  );

  return {
    x: total.x / points.length,
    y: total.y / points.length,
    z: total.z / points.length,
  };
}

function isMirroredPair(points: RegionPoint[]) {
  if (points.length !== 2) return false;
  const [left, right] = [...points].sort((a, b) => a.x - b.x);
  return (
    left.x < 0 &&
    right.x > 0 &&
    Math.abs(Math.abs(left.x) - Math.abs(right.x)) < 0.28 &&
    Math.abs(left.y - right.y) < 0.35 &&
    Math.abs(left.z - right.z) < 0.35
  );
}

function buildRegionNodeSpec(points: RegionPoint[]): RegionNodeSpec {
  const anchors = points.map(scaleRegionPoint);
  const centroid = scaleRegionPoint(averagePoints(points));

  if (points.length <= 1) {
    return { kind: 'single', anchors, centroid };
  }

  if (isMirroredPair(points)) {
    return { kind: 'bilateral', anchors, centroid };
  }

  const xs = points.map((point) => point.x);
  const xSpread = Math.max(...xs) - Math.min(...xs);
  if (points.length >= 4 && xSpread >= 1.8) {
    const leftPoints = points.filter((point) => point.x < 0);
    const rightPoints = points.filter((point) => point.x > 0);
    if (leftPoints.length > 0 && rightPoints.length > 0) {
      return {
        kind: 'split',
        anchors,
        centroid,
        sideAnchors: [leftPoints.map(scaleRegionPoint), rightPoints.map(scaleRegionPoint)],
        sideCentroids: [scaleRegionPoint(averagePoints(leftPoints)), scaleRegionPoint(averagePoints(rightPoints))],
      };
    }
  }

  return { kind: 'cluster', anchors, centroid };
}

// Reusable geometry to keep marker updates light.
const glowGeometry = new THREE.SphereGeometry(0.12, 24, 24);
const hubGeometry = new THREE.SphereGeometry(0.07, 24, 24);
const coreGeometry = new THREE.SphereGeometry(0.038, 20, 20);
const anchorGeometry = new THREE.SphereGeometry(0.04, 18, 18);
const anchorGlowGeometry = new THREE.SphereGeometry(0.07, 18, 18);

function NodeConnections({
  segments,
  color,
  opacity,
}: {
  segments: Array<[ScaledPoint, ScaledPoint]>;
  color: string;
  opacity: number;
}) {
  const geometry = useMemo(() => {
    const buffer: number[] = [];
    segments.forEach(([start, end]) => {
      buffer.push(...start, ...end);
    });
    const nextGeometry = new THREE.BufferGeometry();
    nextGeometry.setAttribute('position', new THREE.Float32BufferAttribute(buffer, 3));
    return nextGeometry;
  }, [segments]);

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

  return (
    <lineSegments geometry={geometry}>
      <lineBasicMaterial color={color} transparent opacity={opacity} depthWrite={false} />
    </lineSegments>
  );
}

function RegionNode({
  positions,
  color,
  label,
  isActive,
  onPointerDown,
  onDoubleClick,
  showLabel,
  mode,
}: {
  positions: RegionPoint[];
  color: string;
  label: string;
  isActive: boolean;
  onPointerDown?: (e: any) => void;
  onDoubleClick?: (e: any) => void;
  showLabel?: boolean;
  mode: 'condition' | 'debug';
}) {
  const spec = useMemo(() => buildRegionNodeSpec(positions), [positions]);
  const hubRefs = useRef<THREE.Mesh[]>([]);
  const haloRefs = useRef<THREE.Mesh[]>([]);

  hubRefs.current = [];
  haloRefs.current = [];

  const registerHub = (mesh: THREE.Mesh | null) => {
    if (mesh && !hubRefs.current.includes(mesh)) hubRefs.current.push(mesh);
  };
  const registerHalo = (mesh: THREE.Mesh | null) => {
    if (mesh && !haloRefs.current.includes(mesh)) haloRefs.current.push(mesh);
  };

  useEffect(() => {
    if (isActive) return;
    hubRefs.current.forEach((mesh) => mesh.scale.setScalar(1));
    haloRefs.current.forEach((mesh) => mesh.scale.set(1, 1, 1));
  }, [isActive]);

  useFrame(({ clock }) => {
    if (!isActive) return;
    const pulse = 1.03 + Math.sin(clock.getElapsedTime() * 2.4) * 0.08;
    hubRefs.current.forEach((mesh) => mesh.scale.setScalar(pulse));
    haloRefs.current.forEach((mesh) => mesh.scale.set(1 + (pulse - 1) * 1.4, 1 + (pulse - 1) * 1.4, 1));
  });

  const connectorColor = mode === 'debug' ? (isActive ? '#60a5fa' : '#cbd5e1') : color;
  const fillColor = mode === 'debug' ? (isActive ? '#2563eb' : '#94a3b8') : color;
  const haloColor = mode === 'debug' ? (isActive ? '#93c5fd' : '#cbd5e1') : color;
  const coreOpacity = mode === 'debug' ? (isActive ? 0.95 : 0.76) : 0.84;
  const labelPosition: ScaledPoint = [spec.centroid[0], spec.centroid[1] + 0.16, spec.centroid[2]];
  const showConnectors = mode === 'debug';

  const renderHub = (position: ScaledPoint, key: string, size: number, glowSize: number) => (
    <group key={key} position={position}>
      <mesh
        ref={registerHalo}
        geometry={glowGeometry}
        scale={[glowSize * (mode === 'condition' ? 1.18 : 0.92), glowSize * (mode === 'condition' ? 1.18 : 0.92), glowSize * (mode === 'condition' ? 1.18 : 0.92)]}
        onPointerDown={onPointerDown}
        onDoubleClick={onDoubleClick}
        userData={{ isMarker: true }}
      >
        <meshBasicMaterial
          color={haloColor}
          transparent
          opacity={mode === 'debug' ? (isActive ? 0.26 : 0.15) : (isActive ? 0.17 : 0.09)}
          depthWrite={false}
        />
      </mesh>
      <mesh
        ref={registerHub}
        geometry={hubGeometry}
        scale={[size, size, size]}
        onPointerDown={onPointerDown}
        onDoubleClick={onDoubleClick}
        userData={{ isMarker: true }}
      >
        <meshPhongMaterial
          color={fillColor}
          emissive={fillColor}
          emissiveIntensity={mode === 'condition' ? (isActive ? 0.7 : 0.3) : 0}
          transparent
          opacity={coreOpacity}
        />
      </mesh>
      {mode === 'condition' && (
        <mesh geometry={coreGeometry} scale={[size * 0.82, size * 0.82, size * 0.82]} onPointerDown={onPointerDown} userData={{ isMarker: true }}>
          <meshStandardMaterial color="#ffffff" emissive="#ffffff" emissiveIntensity={isActive ? 0.2 : 0.08} roughness={0.25} metalness={0.05} transparent opacity={0.92} />
        </mesh>
      )}
    </group>
  );

  const renderAnchor = (position: ScaledPoint, key: string, size = 1) => (
    <group key={key} position={position}>
      {mode === 'condition' && (
        <mesh geometry={anchorGlowGeometry} scale={[size * 0.84, size * 0.84, size * 0.84]} onPointerDown={onPointerDown} userData={{ isMarker: true }}>
          <meshBasicMaterial color={haloColor} transparent opacity={isActive ? 0.11 : 0.07} depthWrite={false} />
        </mesh>
      )}
      <mesh
        geometry={anchorGeometry}
        scale={[size * (mode === 'condition' ? 0.72 : 1), size * (mode === 'condition' ? 0.72 : 1), size * (mode === 'condition' ? 0.72 : 1)]}
        onPointerDown={onPointerDown}
        onDoubleClick={onDoubleClick}
        userData={{ isMarker: true }}
      >
        <meshPhongMaterial
          color={fillColor}
          emissive={fillColor}
          emissiveIntensity={mode === 'condition' ? 0.1 : 0}
          transparent
          opacity={mode === 'debug' ? 0.72 : 0.72}
        />
      </mesh>
      {mode === 'condition' && (
        <mesh geometry={coreGeometry} scale={[size * 0.28, size * 0.28, size * 0.28]} onPointerDown={onPointerDown} userData={{ isMarker: true }}>
          <meshStandardMaterial color="#ffffff" roughness={0.3} metalness={0.04} transparent opacity={0.82} />
        </mesh>
      )}
    </group>
  );

  const clusterSegments =
    spec.kind === 'cluster'
      ? spec.anchors.map((anchor) => [spec.centroid, anchor] as [ScaledPoint, ScaledPoint])
      : [];
  const bilateralSegments =
    spec.kind === 'bilateral'
      ? [[spec.anchors[0], spec.anchors[1]] as [ScaledPoint, ScaledPoint]]
      : [];
  const splitSegments =
    spec.kind === 'split'
      ? [
          ...spec.sideAnchors[0].map((anchor) => [spec.sideCentroids[0], anchor] as [ScaledPoint, ScaledPoint]),
          ...spec.sideAnchors[1].map((anchor) => [spec.sideCentroids[1], anchor] as [ScaledPoint, ScaledPoint]),
          [spec.sideCentroids[0], spec.sideCentroids[1]] as [ScaledPoint, ScaledPoint],
        ]
      : [];

  return (
    <group>
      {spec.kind === 'single' && renderHub(spec.centroid, 'single', 1, 1)}

      {spec.kind === 'bilateral' && (
        <>
          {showConnectors && <NodeConnections segments={bilateralSegments} color={connectorColor} opacity={0.3} />}
          {spec.anchors.map((anchor, index) => renderHub(anchor, `bilateral-${index}`, 0.88, 0.75))}
        </>
      )}

      {spec.kind === 'cluster' && (
        <>
          {showConnectors && <NodeConnections segments={clusterSegments} color={connectorColor} opacity={0.38} />}
          {spec.anchors.map((anchor, index) => renderAnchor(anchor, `cluster-anchor-${index}`, 0.82))}
          {renderHub(spec.centroid, 'cluster-hub', 1.08, 1.28)}
        </>
      )}

      {spec.kind === 'split' && (
        <>
          {showConnectors && <NodeConnections segments={splitSegments} color={connectorColor} opacity={0.34} />}
          {spec.anchors.map((anchor, index) => renderAnchor(anchor, `split-anchor-${index}`, 0.78))}
          {renderHub(spec.sideCentroids[0], 'split-left', 0.92, 0.9)}
          {renderHub(spec.sideCentroids[1], 'split-right', 0.92, 0.9)}
        </>
      )}

      {showLabel && (
        <group position={labelPosition}>
          <Html distanceFactor={8} style={{ pointerEvents: 'none' }}>
            <div
              className="whitespace-nowrap rounded-full border px-2 py-1 text-[9px] font-semibold shadow-sm backdrop-blur-sm"
              style={{
                transform: 'translateY(-4px)',
                background: mode === 'debug' ? 'rgba(239, 246, 255, 0.96)' : 'rgba(255, 255, 255, 0.94)',
                borderColor: mode === 'debug' ? 'rgba(96, 165, 250, 0.6)' : 'rgba(148, 163, 184, 0.22)',
                color: mode === 'debug' ? '#1d4ed8' : '#0f172a',
              }}
            >
              {label}
            </div>
          </Html>
        </group>
      )}
    </group>
  );
}

// Condition marker with click handler and detail panel
function ConditionMarker({ condition, index }: { condition: Condition; index: number }) {
  const currentConditionIndex = useHealthStore((state) => state.currentConditionIndex);
  const setCurrentCondition = useHealthStore((state) => state.setCurrentCondition);
  const openDetailPanel = useHealthStore((state) => state.openDetailPanel);

  const isActive = index === currentConditionIndex;
  const color = CONDITION_COLORS[condition.category as keyof typeof CONDITION_COLORS];
  const positions = condition.bodyRegion && BODY_REGION_POSITIONS[condition.bodyRegion]
    ? BODY_REGION_POSITIONS[condition.bodyRegion].positions
    : condition.position
      ? [condition.position]
      : [];

  if (positions.length === 0) return null;

  const handlePointerDown = (e: any) => {
    e.stopPropagation();
    setCurrentCondition(index);
  };

  const handleDoubleClick = (e: any) => {
    e.stopPropagation();
    setCurrentCondition(index);
    openDetailPanel();
  };

  return (
    <RegionNode
      positions={positions}
      color={color}
      label={condition.name}
      isActive={isActive}
      onPointerDown={handlePointerDown}
      onDoubleClick={handleDoubleClick}
      showLabel={false}
      mode="condition"
    />
  );
}

// ── Debug / calibration ──

const REGION_KEYS = Object.keys(BODY_REGION_POSITIONS);
type RegionPins = Record<string, Array<{ x: number; y: number; z: number }>>;

const pinGeometry = new THREE.SphereGeometry(0.04, 12, 12);

function CalibrationMarker({ positions, isActive, label, color }: {
  positions: RegionPoint[]; isActive: boolean; label: string; color: string;
}) {
  return (
    <RegionNode positions={positions} color={color} label={label} isActive={isActive} showLabel={isActive} mode="debug" />
  );
}

function PinMarker({ position, index }: { position: [number, number, number]; index: number }) {
  return (
    <group position={position}>
      <mesh geometry={pinGeometry} userData={{ isMarker: true }}>
        <meshBasicMaterial color="#ef4444" />
      </mesh>
      <Html distanceFactor={8} style={{ pointerEvents: 'none' }}>
        <div className="whitespace-nowrap rounded bg-red-600 px-1.5 py-0.5 text-[8px] font-mono font-medium text-white" style={{ transform: 'translateX(6px)' }}>
          #{index + 1}
        </div>
      </Html>
    </group>
  );
}

function getBodyHit({
  raycaster,
  camera,
  scene,
  canvas,
  clientX,
  clientY,
}: {
  raycaster: THREE.Raycaster;
  camera: THREE.Camera;
  scene: THREE.Scene;
  canvas: HTMLCanvasElement;
  clientX: number;
  clientY: number;
}) {
  const rect = canvas.getBoundingClientRect();
  const mouse = new THREE.Vector2(
    ((clientX - rect.left) / rect.width) * 2 - 1,
    -((clientY - rect.top) / rect.height) * 2 + 1,
  );

  raycaster.setFromCamera(mouse, camera);
  return raycaster
    .intersectObjects(scene.children, true)
    .find((hit) => hit.object instanceof THREE.Mesh && hit.object.userData.isBodyMesh === true) ?? null;
}

function BodyClickHandler({
  onClickPosition,
  bodyRootRef,
}: {
  onClickPosition: (rawPos: { x: number; y: number; z: number }) => void;
  bodyRootRef: React.MutableRefObject<THREE.Group | null>;
}) {
  const { raycaster, scene, camera, gl } = useThree();

  useEffect(() => {
    const handleClick = (event: MouseEvent) => {
      const hit = getBodyHit({
        raycaster,
        camera,
        scene,
        canvas: gl.domElement,
        clientX: event.clientX,
        clientY: event.clientY,
      });

      if (hit) {
        const p = hit.point.clone();
        bodyRootRef.current?.worldToLocal(p);
        onClickPosition({
          x: +(p.x / MODEL_SCALE).toFixed(2),
          y: +((p.y - MODEL_CENTER_SHIFT_Y) / MODEL_SCALE).toFixed(2),
          z: +(p.z / MODEL_SCALE).toFixed(2),
        });
      }
    };
    gl.domElement.addEventListener('dblclick', handleClick);
    return () => gl.domElement.removeEventListener('dblclick', handleClick);
  }, [raycaster, scene, camera, gl, onClickPosition, bodyRootRef]);

  return null;
}

// ── Projection zoom ──
// Wheel directly updates camera.zoom plus a vertical projection offset.
// That keeps the body's world-space center fixed, avoids dolly jitter, and
// still lets users zoom toward the head or feet based on cursor Y.
const DEFAULT_TARGET_Y = 0;
const DEFAULT_CAMERA_DISTANCE = 7;
const MODEL_CENTER_SHIFT_Y = 1.02;
const PROJECTION_ZOOM_MIN = 1;
const PROJECTION_ZOOM_MAX = 4.5;
const PROJECTION_ZOOM_PER_PIXEL = 0.0016;
const PROJECTION_OFFSET_FACTOR = 0.34;
const PROJECTION_OFFSET_EPSILON = 0.5;

function CursorYZoom() {
  const { gl, camera } = useThree();

  useEffect(() => {
    if (!(camera instanceof THREE.PerspectiveCamera)) return;
    const el = gl.domElement;
    const zoomRef = { current: PROJECTION_ZOOM_MIN };
    const offsetYRef = { current: 0 };

    const applyProjection = () => {
      const width = el.clientWidth || el.getBoundingClientRect().width || 1;
      const height = el.clientHeight || el.getBoundingClientRect().height || 1;

      camera.zoom = zoomRef.current;
      if (Math.abs(offsetYRef.current) < PROJECTION_OFFSET_EPSILON) {
        camera.clearViewOffset();
      } else {
        camera.setViewOffset(width, height, 0, offsetYRef.current, width, height);
      }
      camera.updateProjectionMatrix();
    };

    const onWheel = (e: WheelEvent) => {
      e.preventDefault();
      const normalizedDelta = e.deltaMode === WheelEvent.DOM_DELTA_LINE
        ? e.deltaY * 16
        : e.deltaMode === WheelEvent.DOM_DELTA_PAGE
          ? e.deltaY * el.clientHeight
          : e.deltaY;

      const currentZoom = zoomRef.current;
      const nextZoom = THREE.MathUtils.clamp(
        currentZoom * Math.exp(-normalizedDelta * PROJECTION_ZOOM_PER_PIXEL),
        PROJECTION_ZOOM_MIN,
        PROJECTION_ZOOM_MAX,
      );

      const rect = el.getBoundingClientRect();
      // Screen Y grows downward, but our zoom-anchor math expects positive Y to
      // mean "toward the head / top of the viewport".
      const anchorY = rect.height / 2 - (e.clientY - rect.top);
      const anchoredOffsetY = anchorY - (nextZoom / currentZoom) * (anchorY - offsetYRef.current);
      const maxOffsetY = Math.max(0, (nextZoom - 1) * rect.height * PROJECTION_OFFSET_FACTOR);

      zoomRef.current = nextZoom;
      offsetYRef.current = THREE.MathUtils.clamp(anchoredOffsetY, -maxOffsetY, maxOffsetY);
      applyProjection();
    };

    const resizeObserver = new ResizeObserver(() => {
      applyProjection();
    });
    resizeObserver.observe(el);

    applyProjection();
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => {
      el.removeEventListener('wheel', onWheel);
      resizeObserver.disconnect();
      camera.zoom = 1;
      camera.clearViewOffset();
      camera.updateProjectionMatrix();
    };
  }, [gl, camera]);

  return null;
}

// ── Scene ──

function Scene({ debugMode, renderMode, activeRegionIndex, regionPins, onClickPosition }: {
  debugMode: boolean;
  renderMode: RenderMode;
  activeRegionIndex: number;
  regionPins: RegionPins;
  onClickPosition: (rawPos: { x: number; y: number; z: number }) => void;
}) {
  const conditions = useHealthStore((state) => state.conditions);
  const bodyRootRef = useRef<THREE.Group | null>(null);

  return (
    <>
      {renderMode === 'studio' ? (
        <>
          {/* Studio three-point lighting */}
          <ambientLight intensity={0.35} color="#e8eef7" />
          {/* Key light — front-right, warm */}
          <directionalLight position={[3, 2.5, 4]} intensity={1.4} color="#fff4e6" castShadow />
          {/* Fill light — front-left, cool */}
          <directionalLight position={[-3, 1, 3]} intensity={0.5} color="#dbe7ff" />
          {/* Rim light — behind-top, bright neutral */}
          <directionalLight position={[0, 3, -4]} intensity={1.1} color="#ffffff" />
        </>
      ) : (
        <>
          {/* X-ray body uses an unlit Fresnel shader, so we only need ambient
              light here for the condition markers (meshPhongMaterial). */}
          <ambientLight intensity={1.0} color="#cfe4ff" />
        </>
      )}
      <group ref={bodyRootRef} position={[0, MODEL_CENTER_SHIFT_Y, 0]}>
        <Suspense fallback={<ModelLoader />}>
          <HumanBodyModel objPath="/Male.OBJ" renderMode={renderMode} />
        </Suspense>
        {debugMode ? (
          <>
            {REGION_KEYS.map((rk, i) => {
              const pins = regionPins[rk];
              const isActive = i === activeRegionIndex;
              const hasPins = pins && pins.length > 0;
              if (hasPins) {
                return pins.map((src, pi) => (
                  <PinMarker
                    key={`${rk}-${pi}`}
                    position={[src.x * MODEL_SCALE, src.y * MODEL_SCALE, src.z * MODEL_SCALE]}
                    index={pi}
                  />
                ));
              }

              return (
                <CalibrationMarker
                  key={rk}
                  positions={BODY_REGION_POSITIONS[rk].positions}
                  isActive={isActive}
                  label={BODY_REGION_POSITIONS[rk].label}
                  color={isActive ? '#2563eb' : '#94a3b8'}
                />
              );
            })}
            <BodyClickHandler onClickPosition={onClickPosition} bodyRootRef={bodyRootRef} />
          </>
        ) : (
          conditions.map((condition, index) => (
            <ConditionMarker key={condition.id} condition={condition} index={index} />
          ))
        )}
      </group>
      <OrbitControls
        enableZoom={false}
        enablePan={false}
        enableDamping
        dampingFactor={0.08}
        target={[0, DEFAULT_TARGET_Y, 0]}
        // 360° turntable spin only — vertical tilt locked to the equator.
        minPolarAngle={Math.PI / 2}
        maxPolarAngle={Math.PI / 2}
      />
      <CursorYZoom />
    </>
  );
}

// ── Legend (original) ──

function ConditionLegend() {
  const conditions = useHealthStore((state) => state.conditions);
  const currentConditionIndex = useHealthStore((state) => state.currentConditionIndex);
  const setCurrentCondition = useHealthStore((state) => state.setCurrentCondition);
  const openDetailPanel = useHealthStore((state) => state.openDetailPanel);

  return (
    <div className="condition-legend absolute top-6 right-6 bg-white/95 backdrop-blur-sm rounded-2xl p-4 shadow-lg border border-slate-100 w-56">
      <p className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">Active Conditions</p>
      <div className="space-y-1">
        {conditions.map((condition, index) => (
          <div
            key={condition.id}
            onClick={() => setCurrentCondition(index)}
            onDoubleClick={() => { setCurrentCondition(index); openDetailPanel(); }}
            className={cn(
              'flex items-center gap-2.5 cursor-pointer transition-all p-2 rounded-xl select-none',
              index === currentConditionIndex
                ? 'bg-slate-50'
                : 'opacity-50 hover:opacity-80 hover:bg-slate-50/60'
            )}
          >
            <div
              className="w-2.5 h-2.5 rounded-full shrink-0"
              style={{ backgroundColor: CONDITION_COLORS[condition.category] }}
            />
            <div className="min-w-0">
              <p className="text-xs font-medium text-slate-800 truncate">{condition.name}</p>
              <p className="text-[10px] text-slate-400 font-mono">{condition.code}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── Detail Card (original) ──

function DetailCard() {
  const conditions = useHealthStore((state) => state.conditions);
  const currentConditionIndex = useHealthStore((state) => state.currentConditionIndex);
  const setCurrentCondition = useHealthStore((state) => state.setCurrentCondition);
  const cardRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      const target = event.target as HTMLElement;
      if (cardRef.current && cardRef.current.contains(target)) return;
      if (target.tagName === 'CANVAS') return;
      if (target.closest('button') || target.closest('.condition-legend')) return;
    };

    if (currentConditionIndex >= 0) {
      const timer = setTimeout(() => {
        document.addEventListener('mousedown', handleClickOutside);
      }, 150);
      return () => {
        clearTimeout(timer);
        document.removeEventListener('mousedown', handleClickOutside);
      };
    }
  }, [currentConditionIndex, setCurrentCondition]);

  if (currentConditionIndex < 0 || currentConditionIndex >= conditions.length) return null;
  const condition = conditions[currentConditionIndex];

  return (
    <div className="absolute inset-0 pointer-events-none z-50">
      <div
        ref={cardRef}
        className="absolute bottom-6 left-16 bg-white/95 backdrop-blur-sm rounded-2xl shadow-lg p-4 w-64 border border-slate-100 pointer-events-auto"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-start justify-between gap-2 mb-3">
          <div className="min-w-0">
            <p className="text-xs font-mono text-slate-400 bg-slate-100 px-1.5 py-0.5 rounded-md inline-block mb-1">{condition.code}</p>
            <h3 className="text-sm font-semibold text-slate-800 leading-tight">{condition.name}</h3>
          </div>
          <span className={cn('text-[11px] font-medium px-2 py-0.5 rounded-lg capitalize shrink-0 mt-0.5', getSeverityColor(condition.severity))}>
            {condition.severity.charAt(0).toUpperCase() + condition.severity.slice(1)}
          </span>
        </div>

        <div className="h-px bg-slate-100 mb-3" />

        <div className="space-y-1.5">
          <div className="flex items-center gap-2">
            <span className="text-xs text-slate-400 w-14 shrink-0">Diagnosed</span>
            <span className="text-xs font-medium text-slate-700">{formatDate(condition.diagnosedDate)}</span>
          </div>
          {condition.description && (
            <p className="text-xs text-slate-500 leading-relaxed line-clamp-2">{condition.description}</p>
          )}
        </div>
      </div>
    </div>
  );
}

// ── Main component ──

const RENDER_MODE_KEY = 'hipograf:bodyViz:renderMode';

export function BodyVisualization() {
  const [debugMode, setDebugMode] = useState(false);
  const [renderMode, setRenderMode] = useState<RenderMode>(() => {
    if (typeof window === 'undefined') return 'studio';
    const stored = localStorage.getItem(RENDER_MODE_KEY);
    return stored === 'xray' ? 'xray' : 'studio';
  });
  const [activeRegionIndex, setActiveRegionIndex] = useState(0);
  const [regionPins, setRegionPins] = useState<RegionPins>({});
  const [copied, setCopied] = useState(false);
  const nextCondition = useHealthStore((state) => state.nextCondition);
  const prevCondition = useHealthStore((state) => state.prevCondition);

  useEffect(() => {
    localStorage.setItem(RENDER_MODE_KEY, renderMode);
  }, [renderMode]);

  const placedCount = Object.keys(regionPins).filter(k => regionPins[k].length > 0).length;
  const currentRegionKey = REGION_KEYS[activeRegionIndex];
  const currentRegion = BODY_REGION_POSITIONS[currentRegionKey];
  const currentPins = regionPins[currentRegionKey] || [];

  const handleClickPosition = useCallback((rawPos: { x: number; y: number; z: number }) => {
    setRegionPins(prev => {
      const existing = prev[REGION_KEYS[activeRegionIndex]] || [];
      return { ...prev, [REGION_KEYS[activeRegionIndex]]: [...existing, rawPos] };
    });
  }, [activeRegionIndex]);

  const prevRegion = () => setActiveRegionIndex(i => (i - 1 + REGION_KEYS.length) % REGION_KEYS.length);
  const nextRegion = () => setActiveRegionIndex(i => (i + 1) % REGION_KEYS.length);

  const clearCurrentPins = () => {
    setRegionPins(prev => { const next = { ...prev }; delete next[currentRegionKey]; return next; });
  };

  const copyPositions = () => {
    const pad = (v: number) => (v >= 0 ? ' ' : '') + v.toFixed(2);
    const fmtPos = (p: { x: number; y: number; z: number }) => `{ x: ${pad(p.x)}, y: ${pad(p.y)}, z: ${pad(p.z)} }`;
    const lines = REGION_KEYS.map(k => {
      const pins = regionPins[k];
      const positions = (pins && pins.length > 0) ? pins : BODY_REGION_POSITIONS[k].positions;
      const posStr = positions.map(fmtPos).join(', ');
      return `  '${k}':${' '.repeat(Math.max(1, 18 - k.length))}{ positions: [${posStr}], label: '${BODY_REGION_POSITIONS[k].label}' },`;
    });
    const text = lines.join('\n');
    navigator.clipboard.writeText(text);
    console.log('[Calibration] Positions:\n' + text);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  const resetAll = () => { setRegionPins({}); setActiveRegionIndex(0); };

  return (
    <div className="card relative h-[600px] overflow-hidden">
      {/* Dark backdrop only in x-ray mode — makes the cyan Fresnel glow read */}
      {renderMode === 'xray' && !debugMode && (
        <div
          aria-hidden
          className="absolute inset-0 pointer-events-none"
          style={{
            background:
              'radial-gradient(ellipse at center, #0b1726 0%, #050a14 70%, #02060d 100%)',
          }}
        />
      )}
      {/* Navigation buttons (original) */}
      {!debugMode && (
        <div className="absolute left-5 top-1/2 -translate-y-1/2 flex flex-col gap-2 z-10">
          <button
            onClick={prevCondition}
            className="w-9 h-9 rounded-xl bg-white border border-slate-200 text-slate-500 shadow-sm hover:shadow-md hover:border-slate-300 hover:text-slate-700 transition-all flex items-center justify-center"
          >
            <ChevronLeft className="w-4 h-4" />
          </button>
          <button
            onClick={nextCondition}
            className="w-9 h-9 rounded-xl bg-white border border-slate-200 text-slate-500 shadow-sm hover:shadow-md hover:border-slate-300 hover:text-slate-700 transition-all flex items-center justify-center"
          >
            <ChevronRight className="w-4 h-4" />
          </button>
        </div>
      )}

      {/* Render mode + debug toggles */}
      <div className="absolute bottom-4 right-4 z-20 flex items-center gap-1.5">
        <div className="flex items-center rounded-lg border border-gray-200/60 bg-white/80 backdrop-blur-sm overflow-hidden">
          <button
            onClick={() => setRenderMode('studio')}
            className={cn(
              'flex items-center gap-1 px-2 py-1.5 text-[10px] font-medium transition-colors',
              renderMode === 'studio'
                ? 'bg-blue-50 text-blue-700'
                : 'text-gray-400 hover:text-gray-600'
            )}
          >
            <Lightbulb className="w-3 h-3" />
            Studio
          </button>
          <div className="w-px h-4 bg-gray-200/80" />
          <button
            onClick={() => setRenderMode('xray')}
            className={cn(
              'flex items-center gap-1 px-2 py-1.5 text-[10px] font-medium transition-colors',
              renderMode === 'xray'
                ? 'bg-cyan-50 text-cyan-700'
                : 'text-gray-400 hover:text-gray-600'
            )}
          >
            <Sparkles className="w-3 h-3" />
            X-Ray
          </button>
        </div>
        <button
          onClick={() => { setDebugMode(!debugMode); if (debugMode) resetAll(); }}
          className={cn(
            'flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[10px] font-medium transition-all duration-150 border',
            debugMode
              ? 'bg-amber-50 border-amber-300 text-amber-700'
              : 'bg-white/80 border-gray-200/60 text-gray-400 hover:text-gray-600 hover:border-gray-300 backdrop-blur-sm'
          )}
        >
          <Bug className="w-3 h-3" />
          {debugMode ? 'Exit Debug' : 'Test'}
        </button>
      </div>

      {/* Debug calibration overlay */}
      {debugMode && (
        <>
          <div className="absolute top-4 left-4 right-4 z-20 flex items-start justify-between gap-3">
            <div className="rounded-lg border border-amber-300 bg-amber-50/95 backdrop-blur-sm px-3 py-2.5 flex items-center gap-3">
              <button onClick={prevRegion} className="text-amber-700 hover:text-amber-900 transition-colors">
                <ChevronLeft className="w-4 h-4" />
              </button>
              <div className="text-center min-w-[130px]">
                <div className="flex items-center justify-center gap-1.5">
                  {currentPins.length > 0 && <Check className="w-3 h-3 text-green-600" />}
                  <p className="text-[11px] font-semibold text-amber-900">{currentRegion.label}</p>
                </div>
                <p className="text-[9px] text-amber-600 tabular-nums">{activeRegionIndex + 1} / {REGION_KEYS.length}</p>
                {currentPins.length > 0 && (
                  <p className="text-[8px] text-green-700 mt-0.5">{currentPins.length} pin{currentPins.length > 1 ? 's' : ''} placed</p>
                )}
              </div>
              <button onClick={nextRegion} className="text-amber-700 hover:text-amber-900 transition-colors">
                <ChevronRight className="w-4 h-4" />
              </button>
            </div>

            <div className="rounded-lg border border-gray-200 bg-white/90 backdrop-blur-sm px-3 py-2 text-right space-y-1">
              <p className="text-[9px] text-gray-500 flex items-center gap-1 justify-end">
                <Crosshair className="w-3 h-3" /> Double-click to place pin
              </p>
              <p className="text-[9px] font-medium text-amber-700 tabular-nums">{placedCount} / {REGION_KEYS.length} regions</p>
              {currentPins.length > 0 && (
                <button onClick={clearCurrentPins} className="text-[9px] text-red-400 hover:text-red-600 flex items-center gap-0.5 ml-auto">
                  <Trash2 className="w-2.5 h-2.5" /> Clear {currentPins.length} pin{currentPins.length > 1 ? 's' : ''}
                </button>
              )}
            </div>
          </div>

          <div className="absolute bottom-14 left-4 right-4 z-20 flex items-end justify-between gap-3">
            <div className="flex flex-wrap gap-1 max-w-[70%]">
              {REGION_KEYS.map((rk, i) => {
                const hasPins = regionPins[rk] && regionPins[rk].length > 0;
                return (
                  <button
                    key={rk}
                    onClick={() => setActiveRegionIndex(i)}
                    className={cn(
                      'rounded px-1.5 py-0.5 text-[9px] font-medium transition-all border',
                      i === activeRegionIndex
                        ? 'bg-blue-600 text-white border-blue-600'
                        : hasPins
                          ? 'bg-green-50 text-green-700 border-green-300'
                          : 'bg-white/80 text-gray-500 border-gray-200 hover:border-gray-300'
                    )}
                  >
                    {hasPins ? '\u2713 ' : ''}{BODY_REGION_POSITIONS[rk].label}
                  </button>
                );
              })}
            </div>
            <div className="flex gap-1.5 shrink-0">
              <button onClick={resetAll} className="flex items-center gap-1 rounded-lg border border-gray-200 bg-white/90 px-2 py-1.5 text-[9px] font-medium text-gray-500 hover:text-gray-700 transition-colors">
                <RotateCcw className="w-3 h-3" /> Reset
              </button>
              <button
                onClick={copyPositions}
                className={cn(
                  'flex items-center gap-1 rounded-lg border px-2 py-1.5 text-[9px] font-medium transition-colors',
                  copied ? 'bg-green-50 border-green-300 text-green-700' : 'bg-blue-50 border-blue-300 text-blue-700 hover:bg-blue-100'
                )}
              >
                {copied ? <><Check className="w-3 h-3" /> Copied!</> : <><Copy className="w-3 h-3" /> Copy All</>}
              </button>
            </div>
          </div>
        </>
      )}

      {/* 3D Canvas */}
      <Canvas
        camera={{ position: [0, 0, DEFAULT_CAMERA_DISTANCE], fov: 35 }}
        dpr={[1, 1.5]}
        gl={{
          antialias: true,
          alpha: true,
          failIfMajorPerformanceCaveat: true,
        }}
        onCreated={({ gl }) => {
          gl.domElement.addEventListener('webglcontextlost', (event) => {
            event.preventDefault();
            console.warn('WebGL Context Lost');
          });
          gl.domElement.addEventListener('webglcontextrestored', () => {
            console.log('WebGL Context Restored');
          });
        }}
      >
        <Scene debugMode={debugMode} renderMode={renderMode} activeRegionIndex={activeRegionIndex} regionPins={regionPins} onClickPosition={handleClickPosition} />
      </Canvas>

      {/* Detail Card + Legend (hidden in debug mode) */}
      {!debugMode && (
        <>
          <DetailCard />
          <ConditionLegend />
        </>
      )}
    </div>
  );
}
