/**
 * BraceTransformViewer - 3D Brace Viewer with Real-time Deformation
 * Based on EXPERIMENT_6's brace-transform-playground-v2
 * 
 * Extends BraceModelViewer with the ability to apply ellipsoid deformations
 * in real-time based on transformation parameters.
 */
import { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle } from 'react';
import type { BraceTransformParams } from '../pipeline/BraceInlineEditor';

// Three.js is loaded dynamically
let THREE: any = null;
let STLLoader: any = null;
let GLTFLoader: any = null;
let STLExporter: any = null;
let GLTFExporter: any = null;
let OrbitControls: any = null;

type MarkerInfo = {
  name: string;
  position: [number, number, number];
};

type MarkerMap = Record<string, { x: number; y: number; z: number }>;

export type BraceTransformViewerRef = {
  exportSTL: () => Promise<Blob | null>;
  exportGLB: () => Promise<Blob | null>;
  getModifiedGeometry: () => any;
};

type Props = {
  glbUrl?: string | null;
  stlUrl?: string | null;
  transformParams?: BraceTransformParams;
  autoRotate?: boolean;
  rotationSpeed?: number;
  showMarkers?: boolean;
  showGrid?: boolean;
  onMarkersLoaded?: (markers: MarkerInfo[]) => void;
  onGeometryUpdated?: () => void;
};

const BraceTransformViewer = forwardRef<BraceTransformViewerRef, Props>(({
  glbUrl,
  stlUrl,
  transformParams,
  autoRotate = true,
  rotationSpeed = 0.005,
  showMarkers = false,
  showGrid = false,
  onMarkersLoaded,
  onGeometryUpdated,
}, ref) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const rendererRef = useRef<any>(null);
  const sceneRef = useRef<any>(null);
  const cameraRef = useRef<any>(null);
  const controlsRef = useRef<any>(null);
  const meshRef = useRef<any>(null);
  const baseGeometryRef = useRef<any>(null);
  const gridGroupRef = useRef<any>(null);
  const realWorldScaleRef = useRef<number>(1); // units per cm
  const markersRef = useRef<MarkerMap>({});
  const modelGroupRef = useRef<any>(null);
  const animationFrameRef = useRef<number>(0);
  const autoRotateRef = useRef(autoRotate);
  const rotationSpeedRef = useRef(rotationSpeed);
  const paramsRef = useRef<BraceTransformParams | undefined>(transformParams);
  
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [threeLoaded, setThreeLoaded] = useState(false);
  const [dimensions, setDimensions] = useState<{ width: number; height: number; depth: number } | null>(null);

  // Keep refs in sync with props
  useEffect(() => {
    autoRotateRef.current = autoRotate;
    rotationSpeedRef.current = rotationSpeed;
    if (controlsRef.current) {
      controlsRef.current.autoRotate = autoRotate;
      controlsRef.current.autoRotateSpeed = rotationSpeed * 100;
    }
  }, [autoRotate, rotationSpeed]);

  // Create measurement grid with labels
  const createMeasurementGrid = useCallback((scene: any, unitsPerCm: number, modelHeight: number) => {
    if (!THREE) return;

    // Remove existing grid
    if (gridGroupRef.current) {
      scene.remove(gridGroupRef.current);
      gridGroupRef.current = null;
    }

    const gridGroup = new THREE.Group();
    gridGroup.name = 'measurementGrid';

    // Calculate grid size based on model (add some padding)
    const gridSizeCm = Math.ceil(modelHeight / unitsPerCm / 10) * 10 + 20; // Round up to nearest 10cm + padding
    const gridSizeUnits = gridSizeCm * unitsPerCm;
    const divisionsPerCm = 1;
    const totalDivisions = gridSizeCm * divisionsPerCm;

    // Create XZ grid (floor)
    const gridXZ = new THREE.GridHelper(gridSizeUnits, totalDivisions, 0x666688, 0x444466);
    gridXZ.position.y = -modelHeight / 2 - 10; // Below the model
    gridGroup.add(gridXZ);

    // Create XY grid (back wall) - vertical
    const gridXY = new THREE.GridHelper(gridSizeUnits, totalDivisions, 0x668866, 0x446644);
    gridXY.rotation.x = Math.PI / 2;
    gridXY.position.z = -gridSizeUnits / 4;
    gridGroup.add(gridXY);

    // Add axis lines (thicker)
    const axisMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 });
    
    // X-axis (red)
    const xAxisGeom = new THREE.BufferGeometry().setFromPoints([
      new THREE.Vector3(-gridSizeUnits / 2, -modelHeight / 2 - 10, 0),
      new THREE.Vector3(gridSizeUnits / 2, -modelHeight / 2 - 10, 0)
    ]);
    const xAxis = new THREE.Line(xAxisGeom, new THREE.LineBasicMaterial({ color: 0xff4444 }));
    gridGroup.add(xAxis);

    // Y-axis (green)
    const yAxisGeom = new THREE.BufferGeometry().setFromPoints([
      new THREE.Vector3(0, -modelHeight / 2 - 10, 0),
      new THREE.Vector3(0, modelHeight / 2 + 50, 0)
    ]);
    const yAxis = new THREE.Line(yAxisGeom, new THREE.LineBasicMaterial({ color: 0x44ff44 }));
    gridGroup.add(yAxis);

    // Z-axis (blue)
    const zAxisGeom = new THREE.BufferGeometry().setFromPoints([
      new THREE.Vector3(0, -modelHeight / 2 - 10, -gridSizeUnits / 2),
      new THREE.Vector3(0, -modelHeight / 2 - 10, gridSizeUnits / 2)
    ]);
    const zAxis = new THREE.Line(zAxisGeom, new THREE.LineBasicMaterial({ color: 0x4444ff }));
    gridGroup.add(zAxis);

    // Add measurement tick marks and labels every 10cm on Y-axis
    const tickSize = 5;
    const tickMaterial = new THREE.LineBasicMaterial({ color: 0xffffff });
    
    for (let cm = 0; cm <= gridSizeCm; cm += 10) {
      const y = -modelHeight / 2 - 10 + cm * unitsPerCm;
      if (y > modelHeight / 2 + 50) break;
      
      // Tick mark
      const tickGeom = new THREE.BufferGeometry().setFromPoints([
        new THREE.Vector3(-tickSize, y, 0),
        new THREE.Vector3(tickSize, y, 0)
      ]);
      const tick = new THREE.Line(tickGeom, tickMaterial);
      gridGroup.add(tick);

      // Create text sprite for label
      const canvas = document.createElement('canvas');
      canvas.width = 64;
      canvas.height = 32;
      const ctx = canvas.getContext('2d');
      if (ctx) {
        ctx.fillStyle = '#ffffff';
        ctx.font = 'bold 20px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(`${cm}`, 32, 16);
        
        const texture = new THREE.CanvasTexture(canvas);
        const spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent: true });
        const sprite = new THREE.Sprite(spriteMaterial);
        sprite.position.set(-tickSize - 15, y, 0);
        sprite.scale.set(20, 10, 1);
        gridGroup.add(sprite);
      }
    }

    // Add "cm" label at top
    const cmCanvas = document.createElement('canvas');
    cmCanvas.width = 64;
    cmCanvas.height = 32;
    const cmCtx = cmCanvas.getContext('2d');
    if (cmCtx) {
      cmCtx.fillStyle = '#88ff88';
      cmCtx.font = 'bold 18px Arial';
      cmCtx.textAlign = 'center';
      cmCtx.textBaseline = 'middle';
      cmCtx.fillText('cm', 32, 16);
      
      const cmTexture = new THREE.CanvasTexture(cmCanvas);
      const cmSpriteMaterial = new THREE.SpriteMaterial({ map: cmTexture, transparent: true });
      const cmSprite = new THREE.Sprite(cmSpriteMaterial);
      cmSprite.position.set(-25, modelHeight / 2 + 30, 0);
      cmSprite.scale.set(25, 12, 1);
      gridGroup.add(cmSprite);
    }

    gridGroupRef.current = gridGroup;
    scene.add(gridGroup);
  }, []);

  // Keep params ref in sync
  useEffect(() => {
    paramsRef.current = transformParams;
  }, [transformParams]);

  const modelUrl = glbUrl || stlUrl;
  const modelType = glbUrl ? 'glb' : (stlUrl ? 'stl' : null);

  // Load Three.js dynamically
  useEffect(() => {
    const loadThree = async () => {
      try {
        const threeModule = await import('three');
        THREE = threeModule;
        
        const { STLLoader: STL } = await import('three/examples/jsm/loaders/STLLoader.js');
        STLLoader = STL;
        
        const { GLTFLoader: GLTF } = await import('three/examples/jsm/loaders/GLTFLoader.js');
        GLTFLoader = GLTF;
        
        const { STLExporter: STLE } = await import('three/examples/jsm/exporters/STLExporter.js');
        STLExporter = STLE;
        
        const { GLTFExporter: GLTFE } = await import('three/examples/jsm/exporters/GLTFExporter.js');
        GLTFExporter = GLTFE;
        
        const { OrbitControls: OC } = await import('three/examples/jsm/controls/OrbitControls.js');
        OrbitControls = OC;
        
        setThreeLoaded(true);
      } catch (e) {
        console.error('Failed to load Three.js:', e);
        setError('Failed to load 3D viewer');
      }
    };
    
    loadThree();
    
    return () => {
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
      }
      if (rendererRef.current) {
        rendererRef.current.dispose();
      }
    };
  }, []);

  // Initialize scene
  useEffect(() => {
    if (!threeLoaded || !containerRef.current || rendererRef.current) return;
    
    const container = containerRef.current;
    const width = container.clientWidth;
    const height = container.clientHeight;
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1e1e2e);
    sceneRef.current = scene;
    
    const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 10000);
    camera.position.set(0, 0, 400);
    camera.lookAt(0, 0, 0);
    cameraRef.current = camera;
    
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.0;
    container.appendChild(renderer.domElement);
    rendererRef.current = renderer;
    
    // OrbitControls for manual rotation
    if (OrbitControls) {
      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.autoRotate = autoRotateRef.current;
      controls.autoRotateSpeed = rotationSpeedRef.current * 100;
      controls.enablePan = true;
      controls.enableZoom = true;
      controls.minDistance = 100;
      controls.maxDistance = 800;
      controls.target.set(0, 0, 0);
      controlsRef.current = controls;
    }
    
    // Improved lighting for skin-like appearance with shadows
    // Soft ambient for base illumination
    scene.add(new THREE.AmbientLight(0xffeedd, 0.4));
    
    // Hemisphere light for natural sky/ground coloring
    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d7c6d, 0.5);
    hemiLight.position.set(0, 200, 0);
    scene.add(hemiLight);
    
    // Key light (main light with shadows)
    const keyLight = new THREE.DirectionalLight(0xfff5e8, 1.0);
    keyLight.position.set(150, 200, 300);
    keyLight.castShadow = true;
    keyLight.shadow.mapSize.width = 1024;
    keyLight.shadow.mapSize.height = 1024;
    keyLight.shadow.camera.near = 50;
    keyLight.shadow.camera.far = 1000;
    keyLight.shadow.camera.left = -200;
    keyLight.shadow.camera.right = 200;
    keyLight.shadow.camera.top = 200;
    keyLight.shadow.camera.bottom = -200;
    keyLight.shadow.bias = -0.0005;
    scene.add(keyLight);
    
    // Fill light (softer, opposite side)
    const fillLight = new THREE.DirectionalLight(0xe8f0ff, 0.5);
    fillLight.position.set(-200, 100, -100);
    scene.add(fillLight);
    
    // Rim light (back light for edge definition)
    const rimLight = new THREE.DirectionalLight(0xffffff, 0.4);
    rimLight.position.set(0, 50, -300);
    scene.add(rimLight);
    
    // Bottom fill (subtle, prevents too dark shadows underneath)
    const bottomFill = new THREE.DirectionalLight(0xffe8d0, 0.2);
    bottomFill.position.set(0, -200, 100);
    scene.add(bottomFill);
    
    // Animation loop
    const animate = () => {
      animationFrameRef.current = requestAnimationFrame(animate);
      
      // Update orbit controls
      if (controlsRef.current) {
        controlsRef.current.update();
      }
      
      renderer.render(scene, camera);
    };
    animate();
    
    // Handle resize
    const handleResize = () => {
      if (!container || !renderer || !camera) return;
      const newWidth = container.clientWidth;
      const newHeight = container.clientHeight;
      camera.aspect = newWidth / newHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(newWidth, newHeight);
    };
    
    const resizeObserver = new ResizeObserver(handleResize);
    resizeObserver.observe(container);
    
    return () => {
      resizeObserver.disconnect();
    };
  }, [threeLoaded]);

  // Extract markers from GLB
  const extractMarkers = useCallback((root: any): MarkerMap => {
    const markers: MarkerMap = {};
    
    root.traverse((obj: any) => {
      if (obj.name && obj.name.startsWith('LM_')) {
        const worldPos = new THREE.Vector3();
        obj.getWorldPosition(worldPos);
        markers[obj.name] = { x: worldPos.x, y: worldPos.y, z: worldPos.z };
      }
    });
    
    return markers;
  }, []);

  // Apply ellipsoid deformation to geometry
  const applyDeformation = useCallback((geometry: any, params: BraceTransformParams, markers: MarkerMap) => {
    if (!THREE || !geometry) return geometry;

    // Clone geometry to avoid mutating original
    const deformed = geometry.clone();
    const positions = deformed.getAttribute('position');
    
    if (!positions) return deformed;

    // Compute bounding box for fallback positioning
    deformed.computeBoundingBox();
    const bbox = deformed.boundingBox;
    const minY = bbox?.min?.y || 0;
    const maxY = bbox?.max?.y || 1;
    const minX = bbox?.min?.x || -0.5;
    const maxX = bbox?.max?.x || 0.5;
    const minZ = bbox?.min?.z || -0.5;
    const maxZ = bbox?.max?.z || 0.5;
    
    const centerX = (minX + maxX) / 2;
    const centerZ = (minZ + maxZ) / 2;
    const bboxHeight = maxY - minY;
    const bboxWidth = maxX - minX;
    const bboxDepth = maxZ - minZ;

    // Get brace basis from markers OR fallback to bbox
    const pelvis = markers['LM_PELVIS_CENTER'] || { x: centerX, y: minY, z: centerZ };
    const top = markers['LM_TOP_CENTER'] || { x: centerX, y: maxY, z: centerZ };
    
    const braceHeight = Math.sqrt(
      Math.pow(top.x - pelvis.x, 2) +
      Math.pow(top.y - pelvis.y, 2) +
      Math.pow(top.z - pelvis.z, 2)
    ) || bboxHeight || 1;

    const unitsPerMm = braceHeight / Math.max(1e-6, params.expectedBraceHeightMm);
    
    // Get pad/bay markers - with fallback positions based on bounding box
    // Thoracic pad: right side, upper region
    const thPad = markers['LM_PAD_TH'] || { 
      x: centerX + bboxWidth * 0.35, 
      y: minY + bboxHeight * params.apexNorm, 
      z: centerZ - bboxDepth * 0.1 
    };
    // Thoracic bay: left side, upper region (opposite side of pad)
    const thBay = markers['LM_BAY_TH'] || { 
      x: centerX - bboxWidth * 0.35, 
      y: minY + bboxHeight * params.apexNorm, 
      z: centerZ - bboxDepth * 0.1 
    };
    // Lumbar pad: left side, lower region
    const lumPad = markers['LM_PAD_LUM'] || { 
      x: centerX - bboxWidth * 0.3, 
      y: minY + bboxHeight * params.lumbarApexNorm, 
      z: centerZ 
    };
    // Lumbar bay: right side, lower region
    const lumBay = markers['LM_BAY_LUM'] || { 
      x: centerX + bboxWidth * 0.3, 
      y: minY + bboxHeight * params.lumbarApexNorm, 
      z: centerZ 
    };

    // Severity mapping
    const sev = Math.max(0, Math.min(1, (params.cobbDeg - 15) / 40));
    
    // Pad depth based on severity
    const padDepthMm = (8 + 12 * sev) * params.strengthMult;
    const bayClearMm = padDepthMm * 1.2;
    
    const padDepth = padDepthMm * unitsPerMm;
    const bayClear = bayClearMm * unitsPerMm;

    // Size scale based on severity
    const sizeScale = 0.9 + 0.5 * sev;

    // Define features (pads and bays) - always create them with fallback positions
    const features: Array<{
      center: { x: number; y: number; z: number };
      radii: { x: number; y: number; z: number };
      depth: number;
      direction: 1 | -1;
      falloffPower: number;
    }> = [];

    // Add thoracic pad (push inward on convex side)
    features.push({
      center: {
        x: thPad.x,
        y: pelvis.y + params.apexNorm * braceHeight,
        z: thPad.z,
      },
      radii: { 
        x: 45 * unitsPerMm * sizeScale, 
        y: 90 * unitsPerMm * sizeScale, 
        z: 35 * unitsPerMm * sizeScale 
      },
      depth: padDepth,
      direction: -1,
      falloffPower: 2.0,
    });

    // Add thoracic bay (relief on concave side)
    features.push({
      center: {
        x: thBay.x,
        y: pelvis.y + params.apexNorm * braceHeight,
        z: thBay.z,
      },
      radii: { 
        x: 60 * unitsPerMm * sizeScale, 
        y: 110 * unitsPerMm * sizeScale, 
        z: 55 * unitsPerMm * sizeScale 
      },
      depth: bayClear,
      direction: 1,
      falloffPower: 1.6,
    });

    // Add lumbar pad
    features.push({
      center: {
        x: lumPad.x,
        y: pelvis.y + params.lumbarApexNorm * braceHeight,
        z: lumPad.z,
      },
      radii: { 
        x: 50 * unitsPerMm * sizeScale, 
        y: 80 * unitsPerMm * sizeScale, 
        z: 40 * unitsPerMm * sizeScale 
      },
      depth: padDepth * 0.9,
      direction: -1,
      falloffPower: 2.0,
    });

    // Add lumbar bay
    features.push({
      center: {
        x: lumBay.x,
        y: pelvis.y + params.lumbarApexNorm * braceHeight,
        z: lumBay.z,
      },
      radii: { 
        x: 65 * unitsPerMm * sizeScale, 
        y: 95 * unitsPerMm * sizeScale, 
        z: 55 * unitsPerMm * sizeScale 
      },
      depth: bayClear * 0.9,
      direction: 1,
      falloffPower: 1.6,
    });

    // Add hip anchors - with fallback positions
    const hipL = markers['LM_ANCHOR_HIP_L'] || {
      x: centerX - bboxWidth * 0.4,
      y: minY + bboxHeight * 0.1,
      z: centerZ,
    };
    const hipR = markers['LM_ANCHOR_HIP_R'] || {
      x: centerX + bboxWidth * 0.4,
      y: minY + bboxHeight * 0.1,
      z: centerZ,
    };
    const hipDepth = params.hipAnchorStrengthMm * unitsPerMm * params.strengthMult;

    if (hipDepth > 0) {
      features.push({
        center: { x: hipL.x, y: hipL.y, z: hipL.z },
        radii: { 
          x: 35 * unitsPerMm, 
          y: 55 * unitsPerMm, 
          z: 35 * unitsPerMm 
        },
        depth: hipDepth,
        direction: -1,
        falloffPower: 2.2,
      });

      features.push({
        center: { x: hipR.x, y: hipR.y, z: hipR.z },
        radii: { 
          x: 35 * unitsPerMm, 
          y: 55 * unitsPerMm, 
          z: 35 * unitsPerMm 
        },
        depth: hipDepth,
        direction: -1,
        falloffPower: 2.2,
      });
    }

    // Apply deformations
    for (let i = 0; i < positions.count; i++) {
      let x = positions.getX(i);
      let y = positions.getY(i);
      let z = positions.getZ(i);

      // Mirror X if enabled
      if (params.mirrorX) {
        x = -x;
      }

      // Apply trunk shift
      const heightNorm = Math.max(0, Math.min(1, (y - pelvis.y) / braceHeight));
      x += params.trunkShiftMm * unitsPerMm * heightNorm * 0.8;

      // Apply ellipsoid deformations
      for (const feature of features) {
        const dx = (x - feature.center.x) / feature.radii.x;
        const dy = (y - feature.center.y) / feature.radii.y;
        const dz = (z - feature.center.z) / feature.radii.z;

        const d2 = dx * dx + dy * dy + dz * dz;
        if (d2 >= 1) continue;

        // Smooth falloff
        const t = Math.pow(1 - d2, feature.falloffPower);
        const displacement = feature.depth * t * feature.direction;

        // Apply based on push mode
        if (params.pushMode === 'radial') {
          // Radial: push away from/toward brace axis
          const axisPoint = { x: pelvis.x, y: y, z: pelvis.z };
          const radialX = x - axisPoint.x;
          const radialZ = z - axisPoint.z;
          const radialLen = Math.sqrt(radialX * radialX + radialZ * radialZ) || 1;
          
          x += (radialX / radialLen) * displacement;
          z += (radialZ / radialLen) * displacement;
        } else if (params.pushMode === 'lateral') {
          // Lateral: purely left/right
          const side = Math.sign(x - pelvis.x) || 1;
          x += side * displacement;
        } else {
          // Normal: would require vertex normals, approximate with radial
          const axisPoint = { x: pelvis.x, y: y, z: pelvis.z };
          const radialX = x - axisPoint.x;
          const radialZ = z - axisPoint.z;
          const radialLen = Math.sqrt(radialX * radialX + radialZ * radialZ) || 1;
          
          x += (radialX / radialLen) * displacement;
          z += (radialZ / radialLen) * displacement;
        }
      }

      positions.setXYZ(i, x, y, z);
    }

    positions.needsUpdate = true;
    deformed.computeVertexNormals();
    
    return deformed;
  }, []);

  // Load model
  useEffect(() => {
    if (!threeLoaded || !modelUrl || !sceneRef.current) return;
    
    setLoading(true);
    setError(null);
    
    // Clear previous model
    if (modelGroupRef.current) {
      sceneRef.current.remove(modelGroupRef.current);
      modelGroupRef.current = null;
    }
    meshRef.current = null;
    baseGeometryRef.current = null;
    markersRef.current = {};
    
    const group = new THREE.Group();
    modelGroupRef.current = group;

    const onLoadGLB = (gltf: any) => {
      // Extract markers
      markersRef.current = extractMarkers(gltf.scene);
      
      // Notify parent of markers
      if (onMarkersLoaded) {
        const markerList: MarkerInfo[] = Object.entries(markersRef.current).map(([name, pos]) => ({
          name,
          position: [pos.x, pos.y, pos.z],
        }));
        onMarkersLoaded(markerList);
      }
      
      // Find the main mesh
      let mainMesh: any = null;
      let maxVertices = 0;
      
      gltf.scene.traverse((child: any) => {
        if (child.isMesh && !child.name.startsWith('LM_')) {
          const count = child.geometry.getAttribute('position')?.count || 0;
          if (count > maxVertices) {
            maxVertices = count;
            mainMesh = child;
          }
        }
      });
      
      if (!mainMesh) {
        setError('No mesh found in model');
        setLoading(false);
        return;
      }
      
      // Store base geometry
      baseGeometryRef.current = mainMesh.geometry.clone();
      
      // Create mesh with skin-like material
      const material = new THREE.MeshStandardMaterial({
        color: 0xf5d0c5,  // Warm skin tone (light peach/beige)
        roughness: 0.7,    // Slightly smooth for skin-like appearance
        metalness: 0.0,
        side: THREE.DoubleSide,
        flatShading: false,
      });
      
      const mesh = new THREE.Mesh(baseGeometryRef.current.clone(), material);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      meshRef.current = mesh;
      
      // Center and scale
      const box = new THREE.Box3().setFromObject(mesh);
      const center = box.getCenter(new THREE.Vector3());
      const size = box.getSize(new THREE.Vector3());
      
      mesh.position.sub(center);
      
      const maxDim = Math.max(size.x, size.y, size.z);
      const scale = 200 / maxDim;
      mesh.scale.multiplyScalar(scale);
      
      // Calculate real-world dimensions (assuming model units are mm)
      // Typical brace height is 350-450mm, width 250-350mm
      const scaledSize = {
        x: size.x * scale,
        y: size.y * scale,
        z: size.z * scale,
      };
      
      // Store units per cm for grid (model is in mm, so 10mm = 1cm)
      const unitsPerCm = scale * 10; // scale * 10mm
      realWorldScaleRef.current = unitsPerCm;
      
      // Store dimensions in cm
      setDimensions({
        width: Math.round(size.x / 10 * 10) / 10,  // X dimension in cm
        height: Math.round(size.y / 10 * 10) / 10, // Y dimension in cm  
        depth: Math.round(size.z / 10 * 10) / 10,  // Z dimension in cm
      });
      
      // Create measurement grid if enabled
      if (showGrid && sceneRef.current) {
        createMeasurementGrid(sceneRef.current, unitsPerCm, scaledSize.y);
      }
      
      // Scale markers to match
      const scaledMarkers: MarkerMap = {};
      for (const [name, pos] of Object.entries(markersRef.current)) {
        scaledMarkers[name] = {
          x: (pos.x - center.x) * scale,
          y: (pos.y - center.y) * scale,
          z: (pos.z - center.z) * scale,
        };
      }
      markersRef.current = scaledMarkers;
      
      // Add marker spheres if enabled
      if (showMarkers) {
        Object.entries(scaledMarkers).forEach(([name, pos]) => {
          const sphereGeom = new THREE.SphereGeometry(3, 16, 16);
          const sphereMat = new THREE.MeshBasicMaterial({ 
            color: name.includes('PAD') ? 0x00ff00 : 
                   name.includes('BAY') ? 0x0088ff :
                   name.includes('HIP') ? 0xff8800 : 0xff0000,
            transparent: true,
            opacity: 0.8,
          });
          const sphere = new THREE.Mesh(sphereGeom, sphereMat);
          sphere.position.set(pos.x, pos.y, pos.z);
          sphere.name = `marker_${name}`;
          group.add(sphere);
        });
      }
      
      group.add(mesh);
      group.position.set(0, 0, 0);
      sceneRef.current.add(group);
      
      // Apply initial deformation if params provided
      if (transformParams) {
        applyTransformToMesh(transformParams);
      }
      
      cameraRef.current.position.set(0, 0, 350);
      cameraRef.current.lookAt(0, 0, 0);
      
      setLoading(false);
    };

    const onLoadSTL = (geometry: any) => {
      geometry.center();
      geometry.computeVertexNormals();
      
      // Store base geometry
      baseGeometryRef.current = geometry.clone();
      
      // Skin-like material matching GLB loader
      const material = new THREE.MeshStandardMaterial({
        color: 0xf5d0c5,  // Warm skin tone (light peach/beige)
        roughness: 0.7,    // Slightly smooth for skin-like appearance
        metalness: 0.0,
        side: THREE.DoubleSide,
        flatShading: false,
      });
      
      const mesh = new THREE.Mesh(geometry, material);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      meshRef.current = mesh;
      
      // Scale to fit
      const box = new THREE.Box3().setFromObject(mesh);
      const size = box.getSize(new THREE.Vector3());
      const maxDim = Math.max(size.x, size.y, size.z);
      const scale = 200 / maxDim;
      mesh.scale.multiplyScalar(scale);
      
      // Calculate real-world dimensions (assuming model units are mm)
      const scaledSize = {
        x: size.x * scale,
        y: size.y * scale,
        z: size.z * scale,
      };
      
      // Store units per cm for grid (model is in mm, so 10mm = 1cm)
      const unitsPerCm = scale * 10;
      realWorldScaleRef.current = unitsPerCm;
      
      // Store dimensions in cm
      setDimensions({
        width: Math.round(size.x / 10 * 10) / 10,
        height: Math.round(size.y / 10 * 10) / 10,
        depth: Math.round(size.z / 10 * 10) / 10,
      });
      
      // Create measurement grid if enabled
      if (showGrid && sceneRef.current) {
        createMeasurementGrid(sceneRef.current, unitsPerCm, scaledSize.y);
      }
      
      group.add(mesh);
      group.position.set(0, 0, 0);
      sceneRef.current.add(group);
      
      cameraRef.current.position.set(0, 0, 350);
      cameraRef.current.lookAt(0, 0, 0);
      
      setLoading(false);
    };

    const onError = (err: any) => {
      console.error('Failed to load model:', err);
      setError('Failed to load 3D model');
      setLoading(false);
    };
    
    if (modelType === 'stl') {
      const loader = new STLLoader();
      loader.load(modelUrl, onLoadSTL, undefined, onError);
    } else if (modelType === 'glb') {
      const loader = new GLTFLoader();
      loader.load(modelUrl, onLoadGLB, undefined, onError);
    }
  }, [threeLoaded, modelUrl, modelType, showMarkers, showGrid, extractMarkers, onMarkersLoaded, createMeasurementGrid]);

  // Apply transform when params change
  const applyTransformToMesh = useCallback((params: BraceTransformParams) => {
    if (!meshRef.current || !baseGeometryRef.current || !THREE) return;
    
    const deformedGeometry = applyDeformation(
      baseGeometryRef.current.clone(),
      params,
      markersRef.current
    );
    
    meshRef.current.geometry.dispose();
    meshRef.current.geometry = deformedGeometry;
    
    if (onGeometryUpdated) {
      onGeometryUpdated();
    }
  }, [applyDeformation, onGeometryUpdated]);

  // Watch for transform params changes
  useEffect(() => {
    if (transformParams && meshRef.current && baseGeometryRef.current) {
      applyTransformToMesh(transformParams);
    }
  }, [transformParams, applyTransformToMesh]);

  // Export functions
  useImperativeHandle(ref, () => ({
    exportSTL: async () => {
      if (!meshRef.current || !STLExporter) return null;
      
      const exporter = new STLExporter();
      const stlString = exporter.parse(meshRef.current, { binary: true });
      return new Blob([stlString], { type: 'application/octet-stream' });
    },
    
    exportGLB: async () => {
      if (!meshRef.current || !GLTFExporter) return null;
      
      return new Promise((resolve) => {
        const exporter = new GLTFExporter();
        exporter.parse(
          meshRef.current,
          (result: any) => {
            const blob = new Blob([result], { type: 'application/octet-stream' });
            resolve(blob);
          },
          (error: any) => {
            console.error('GLB export error:', error);
            resolve(null);
          },
          { binary: true }
        );
      });
    },
    
    getModifiedGeometry: () => {
      return meshRef.current?.geometry || null;
    },
  }), []);

  if (!threeLoaded) {
    return (
      <div className="brace-transform-viewer-loading">
        <div className="spinner"></div>
        <p>Loading 3D viewer...</p>
      </div>
    );
  }

  return (
    <div className="brace-transform-viewer-container">
      <div 
        ref={containerRef} 
        className="brace-transform-viewer-canvas"
      />
      
      {/* Dimensions overlay */}
      {showGrid && dimensions && !loading && (
        <div className="brace-dimensions-overlay">
          <div className="dimensions-title">Dimensions (cm)</div>
          <div className="dimension-row">
            <span className="dim-label">Width:</span>
            <span className="dim-value">{dimensions.width.toFixed(1)}</span>
          </div>
          <div className="dimension-row">
            <span className="dim-label">Height:</span>
            <span className="dim-value">{dimensions.height.toFixed(1)}</span>
          </div>
          <div className="dimension-row">
            <span className="dim-label">Depth:</span>
            <span className="dim-value">{dimensions.depth.toFixed(1)}</span>
          </div>
        </div>
      )}
      
      {loading && (
        <div className="brace-transform-viewer-overlay">
          <div className="spinner"></div>
          <p>Loading brace model...</p>
        </div>
      )}
      
      {error && (
        <div className="brace-transform-viewer-overlay error">
          <p>{error}</p>
        </div>
      )}
      
      {!modelUrl && !loading && (
        <div className="brace-transform-viewer-overlay placeholder">
          <div className="placeholder-icon">🦾</div>
          <p>Generate a brace to see 3D preview</p>
        </div>
      )}
    </div>
  );
});

BraceTransformViewer.displayName = 'BraceTransformViewer';

export default BraceTransformViewer;
