'use client'

import React, { useEffect, useRef, useState } from 'react'
 
import {
  Engine,
  Scene,
  Vector3,
  HemisphericLight,
  ArcRotateCamera,
  Color3,
  Color4,
  AbstractMesh,
  Nullable,
  HighlightLayer, 
  Animation,
  CubicEase,
  EasingFunction, 
  ImportMeshAsync,
  PointerEventTypes,
  PointerInfo,
  Matrix,
  Ray,
} from '@babylonjs/core'
import '@babylonjs/loaders'
 
import SceneToolbar from './SceneToolbar';
import LoadingSpinner from '../ui/LoadingSpinner'
import useNavigationStore from '@/app/store/navigationStore'
import {
  getSensorIdFromMesh,
  collectSensorMeshes,
  applyHighlightToMeshes,
  statusToColor3,
} from './sensorHighlight'
import {
  computeSensorOverlayCircles,
  hexWithAlpha,
} from './sensorHighlightOverlay'
 
export interface ModelViewerProps {
  modelPath: string
  onSelectModel: (path: string) => void;
  onModelLoaded?: (modelData: {
    meshes: AbstractMesh[]
    boundingBox: {
      min: { x: number; y: number; z: number }
      max: { x: number; y: number; z: number }
    }
  }) => void
  onError?: (error: string) => void 
  activeMenu?: string | null
  focusSensorId?: string | null
  renderOverlay?: (params: { anchor: { left: number; top: number } | null; info?: { name?: string; sensorId?: string } | null }) => React.ReactNode
  isSensorSelectionEnabled?: boolean
  onSensorPick?: (sensorId: string | null) => void
  highlightAllSensors?: boolean
  sensorStatusMap?: Record<string, string>
}

const ModelViewer: React.FC<ModelViewerProps> = ({
  modelPath,
  onSelectModel,
  onModelLoaded,
  onError,
  focusSensorId,
  renderOverlay,
  isSensorSelectionEnabled,
  onSensorPick,
  highlightAllSensors,
  sensorStatusMap,
  showStats = false,
  onToggleStats,
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const engineRef = useRef<Nullable<Engine>>(null)
  const sceneRef = useRef<Nullable<Scene>>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [loadingProgress, setLoadingProgress] = useState(0)
  const [showModel, setShowModel] = useState(false)
  const isInitializedRef = useRef(false)
  const isDisposedRef = useRef(false)
  const importedMeshesRef = useRef<AbstractMesh[]>([])
  const highlightLayerRef = useRef<HighlightLayer | null>(null)
  const highlightedMeshesRef = useRef<AbstractMesh[]>([])
  const chosenMeshRef = useRef<AbstractMesh | null>(null)
  const [overlayPos, setOverlayPos] = useState<{ left: number; top: number } | null>(null)
  const [overlayData, setOverlayData] = useState<{ name?: string; sensorId?: string } | null>(null)
  const [modelReady, setModelReady] = useState(false)
  const [panActive, setPanActive] = useState(false);
  const [webglError, setWebglError] = useState<string | null>(null)
  const [allSensorsOverlayCircles, setAllSensorsOverlayCircles] = useState<
    { sensorId: string; left: number; top: number; colorHex: string }[]
  >([])
  // NEW: State for tracking hovered sensor in overlay circles
  const [hoveredSensorId, setHoveredSensorId] = useState<string | null>(null)

  const handlePan = () => setPanActive(!panActive);

  useEffect(() => {
    const scene = sceneRef.current;
    const camera = scene?.activeCamera as ArcRotateCamera;
    const canvas = canvasRef.current;

    if (!scene || !camera || !canvas) {
      return;
    }

    let observer: any = null;

    if (panActive) { 
      camera.detachControl();

      observer = scene.onPointerObservable.add((pointerInfo: PointerInfo) => {
        const evt = pointerInfo.event;

        if (evt.buttons === 1) {
          camera.inertialPanningX -= evt.movementX / camera.panningSensibility;
          camera.inertialPanningY += evt.movementY / camera.panningSensibility;
        }
        else if (evt.buttons === 2) {
          camera.inertialAlphaOffset -= evt.movementX / camera.angularSensibilityX;
          camera.inertialBetaOffset -= evt.movementY / camera.angularSensibilityY;
        }
      }, PointerEventTypes.POINTERMOVE);

    } else {
      camera.detachControl();
      camera.attachControl(canvas, true);
    }

    return () => {
      if (observer) {
        scene.onPointerObservable.remove(observer);
      }
      if (!camera.isDisposed() && !camera.inputs.attachedToElement) {
        camera.attachControl(canvas, true);
      }
    };
  }, [panActive, sceneRef, canvasRef]);

  const handleZoomIn = () => {
     const camera = sceneRef.current?.activeCamera as ArcRotateCamera
     if (camera) {
       sceneRef.current?.stopAnimation(camera)
       const ease = new CubicEase()
       ease.setEasingMode(EasingFunction.EASINGMODE_EASEOUT)
 
       const frameRate = 60
       const durationMs = 300
       const totalFrames = Math.round((durationMs / 1000) * frameRate)
 
       const currentRadius = camera.radius
       const targetRadius = Math.max(camera.lowerRadiusLimit ?? 0.1, currentRadius * 0.8)
 
       Animation.CreateAndStartAnimation(
         'zoomIn',
         camera,
         'radius',
         frameRate,
         totalFrames,
         currentRadius,
         targetRadius,
         Animation.ANIMATIONLOOPMODE_CONSTANT,
         ease
       )
     }
   }
    const handleZoomOut = () => {
     const camera = sceneRef.current?.activeCamera as ArcRotateCamera
     if (camera) {
       sceneRef.current?.stopAnimation(camera)
       const ease = new CubicEase()
       ease.setEasingMode(EasingFunction.EASINGMODE_EASEOUT)
 
       const frameRate = 60
       const durationMs = 300
       const totalFrames = Math.round((durationMs / 1000) * frameRate)
 
       const currentRadius = camera.radius
       const targetRadius = Math.min(camera.upperRadiusLimit ?? Infinity, currentRadius * 1.2)
 
       Animation.CreateAndStartAnimation(
         'zoomOut',
         camera,
         'radius',
         frameRate,
         totalFrames,
         currentRadius,
         targetRadius,
         Animation.ANIMATIONLOOPMODE_CONSTANT,
         ease
       )
     }
   }
    const handleTopView = () => {
      const camera = sceneRef.current?.activeCamera as ArcRotateCamera;
      if (camera) {
        sceneRef.current?.stopAnimation(camera);
        const ease = new CubicEase();
        ease.setEasingMode(EasingFunction.EASINGMODE_EASEOUT);

        const frameRate = 60;
        const durationMs = 500;
        const totalFrames = Math.round((durationMs / 1000) * frameRate);

        Animation.CreateAndStartAnimation(
          'topViewAlpha',
          camera,
          'alpha',
          frameRate,
          totalFrames,
          camera.alpha,
          Math.PI / 2,
          Animation.ANIMATIONLOOPMODE_CONSTANT,
          ease
        );

        Animation.CreateAndStartAnimation(
          'topViewBeta',
          camera,
          'beta',
          frameRate,
          totalFrames,
          camera.beta,
          0,
          Animation.ANIMATIONLOOPMODE_CONSTANT,
          ease
        );
      }
    };

  // NEW: Function to handle overlay circle click
  const handleOverlayCircleClick = (sensorId: string) => {
    console.log('[ModelViewer] Overlay circle clicked:', sensorId)
    
    // Find the mesh for this sensor
    const allMeshes = importedMeshesRef.current || []
    const sensorMeshes = collectSensorMeshes(allMeshes)
    const targetMesh = sensorMeshes.find(m => getSensorIdFromMesh(m) === sensorId)

    if (!targetMesh) {
      console.warn(`[ModelViewer] Mesh not found for sensor: ${sensorId}`)
      return
    }

    const scene = sceneRef.current
    const camera = scene?.activeCamera as ArcRotateCamera
    if (!scene || !camera) return

    // Calculate bounding box of the sensor mesh
    const bbox = (typeof targetMesh.getHierarchyBoundingVectors === 'function')
      ? targetMesh.getHierarchyBoundingVectors()
      : { 
          min: targetMesh.getBoundingInfo().boundingBox.minimumWorld, 
          max: targetMesh.getBoundingInfo().boundingBox.maximumWorld 
        }

    const center = bbox.min.add(bbox.max).scale(0.5)
    const size = bbox.max.subtract(bbox.min)
    const maxDimension = Math.max(size.x, size.y, size.z)
    
    // Calculate optimal camera distance
    const targetRadius = Math.max(camera.lowerRadiusLimit ?? 2, maxDimension * 1.5)

    // Stop any current animations
    scene.stopAnimation(camera)

    // Setup easing
    const ease = new CubicEase()
    ease.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT)
    
    const frameRate = 60
    const durationMs = 600  // 0.6 seconds for smooth animation
    const totalFrames = Math.round((durationMs / 1000) * frameRate)

    // Animate camera target position
    Animation.CreateAndStartAnimation(
      'camTarget',
      camera,
      'target',
      frameRate,
      totalFrames,
      camera.target.clone(),
      center.clone(),
      Animation.ANIMATIONLOOPMODE_CONSTANT,
      ease
    )

    // Animate camera radius (zoom)
    Animation.CreateAndStartAnimation(
      'camRadius',
      camera,
      'radius',
      frameRate,
      totalFrames,
      camera.radius,
      targetRadius,
      Animation.ANIMATIONLOOPMODE_CONSTANT,
      ease
    )

    // Call callback to display tooltip
    onSensorPick?.(sensorId)
    
    console.log('[ModelViewer] Camera animation started for sensor:', sensorId)
  }
   
  useEffect(() => {
    isDisposedRef.current = false
    isInitializedRef.current = false
    return () => {
      isDisposedRef.current = true
    }
  }, [])

  useEffect(() => {
    if (!canvasRef.current || isInitializedRef.current) return

    const canvas = canvasRef.current
    setWebglError(null)

    let hasWebGL = false
    try {
      const testCanvas = document.createElement('canvas')
      const gl =
        testCanvas.getContext('webgl2') ||
        testCanvas.getContext('webgl') ||
        testCanvas.getContext('experimental-webgl')
      hasWebGL = !!gl
    } catch {
      hasWebGL = false
    }

    if (!hasWebGL) {
      const message = 'WebGL не поддерживается в текущем окружении'
      setWebglError(message)
      onError?.(message)
      setIsLoading(false)
      setModelReady(false)
      return
    }

    let engine: Engine
    try {
      // Оптимизация: используем FXAA вместо MSAA для снижения нагрузки на GPU
      engine = new Engine(canvas, false, { stencil: true }) // false = отключаем MSAA
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error)
      const message = `WebGL недоступен: ${errorMessage}`
      setWebglError(message)
      onError?.(message)
      setIsLoading(false)
      setModelReady(false)
      return
    }
    engineRef.current = engine

    engine.runRenderLoop(() => {
      if (!isDisposedRef.current && sceneRef.current) {
        sceneRef.current.render()
      }
    })

    const scene = new Scene(engine)
    sceneRef.current = scene

    scene.clearColor = new Color4(0.1, 0.1, 0.15, 1)
    
    // Оптимизация: включаем FXAA (более легковесное сглаживание)
    scene.imageProcessingConfiguration.fxaaEnabled = true

    const camera = new ArcRotateCamera('camera', 0, Math.PI / 3, 20, Vector3.Zero(), scene)
    camera.attachControl(canvas, true)
    camera.lowerRadiusLimit = 2
    camera.upperRadiusLimit = 200
    camera.wheelDeltaPercentage = 0.01
    camera.panningSensibility = 50
    camera.angularSensibilityX = 1000
    camera.angularSensibilityY = 1000

    const ambientLight = new HemisphericLight('ambientLight', new Vector3(0, 1, 0), scene)
    ambientLight.intensity = 0.4
    ambientLight.diffuse = new Color3(0.7, 0.7, 0.8)
    ambientLight.specular = new Color3(0.2, 0.2, 0.3)
    ambientLight.groundColor = new Color3(0.3, 0.3, 0.4)

    const keyLight = new HemisphericLight('keyLight', new Vector3(1, 1, 0), scene)
    keyLight.intensity = 0.6
    keyLight.diffuse = new Color3(1, 1, 0.9)
    keyLight.specular = new Color3(1, 1, 0.9)

    const fillLight = new HemisphericLight('fillLight', new Vector3(-1, 0.5, -1), scene)
    fillLight.intensity = 0.3
    fillLight.diffuse = new Color3(0.8, 0.8, 1)
 
    const hl = new HighlightLayer('highlight-layer', scene, {
      mainTextureRatio: 1,
      blurTextureSizeRatio: 1,
    })
    hl.innerGlow = false
    hl.outerGlow = true
    hl.blurHorizontalSize = 2
    hl.blurVerticalSize = 2
    highlightLayerRef.current = hl

    const handleResize = () => {
      if (!isDisposedRef.current) {
        engine.resize()
      }
    }
    window.addEventListener('resize', handleResize)

    isInitializedRef.current = true

    return () => {
      isDisposedRef.current = true
      isInitializedRef.current = false
      window.removeEventListener('resize', handleResize)
 
      highlightLayerRef.current?.dispose()
      highlightLayerRef.current = null
      if (engineRef.current) {
        engineRef.current.dispose()
        engineRef.current = null
      }
      sceneRef.current = null
    }
  }, [onError])

  useEffect(() => {
    if (!modelPath || !sceneRef.current || !engineRef.current) return

    const scene = sceneRef.current

    setIsLoading(true)
    setLoadingProgress(0)
    setShowModel(false)
    setModelReady(false)

    const loadModel = async () => {
      try {
        console.log('[ModelViewer] Starting to load model:', modelPath)
        
        // UI элемент загрузчика (есть эффект замедленности)
        const progressInterval = setInterval(() => {
          setLoadingProgress(prev => {
            if (prev >= 90) {
              clearInterval(progressInterval)
              return 90
            }
            return prev + Math.random() * 15
          })
        }, 100)
        
        // Use the correct ImportMeshAsync signature: (url, scene, onProgress)
        const result = await ImportMeshAsync(modelPath, scene, (evt) => {
          if (evt.lengthComputable) {
            const progress = (evt.loaded / evt.total) * 100
            setLoadingProgress(progress)
            console.log('[ModelViewer] Loading progress:', progress)
          }
        })
        
        clearInterval(progressInterval)

        if (isDisposedRef.current) {
          console.log('[ModelViewer] Component disposed during load')
          return
        }

        console.log('[ModelViewer] Model loaded successfully:', {
          meshesCount: result.meshes.length,
          particleSystemsCount: result.particleSystems.length,
          skeletonsCount: result.skeletons.length,
          animationGroupsCount: result.animationGroups.length
        })

        importedMeshesRef.current = result.meshes

        if (result.meshes.length > 0) {
          const boundingBox = result.meshes[0].getHierarchyBoundingVectors()
          
          onModelLoaded?.({
            meshes: result.meshes,
            boundingBox: {
              min: { x: boundingBox.min.x, y: boundingBox.min.y, z: boundingBox.min.z },
              max: { x: boundingBox.max.x, y: boundingBox.max.y, z: boundingBox.max.z },
            },
          })

          // Автоматическое кадрирование камеры для отображения всей модели
          const camera = scene.activeCamera as ArcRotateCamera
          if (camera) {
            const center = boundingBox.min.add(boundingBox.max).scale(0.5)
            const size = boundingBox.max.subtract(boundingBox.min)
            const maxDimension = Math.max(size.x, size.y, size.z)
            
            // Устанавливаем оптимальное расстояние камеры
            const targetRadius = maxDimension * 2.5 // Множитель для комфортного отступа
            
            // Плавная анимация камеры к центру модели
            scene.stopAnimation(camera)
            
            const ease = new CubicEase()
            ease.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT)
            
            const frameRate = 60
            const durationMs = 800 // 0.8 секунды
            const totalFrames = Math.round((durationMs / 1000) * frameRate)
            
            // Анимация позиции камеры
            Animation.CreateAndStartAnimation(
              'frameCameraTarget',
              camera,
              'target',
              frameRate,
              totalFrames,
              camera.target.clone(),
              center.clone(),
              Animation.ANIMATIONLOOPMODE_CONSTANT,
              ease
            )
            
            // Анимация зума
            Animation.CreateAndStartAnimation(
              'frameCameraRadius',
              camera,
              'radius',
              frameRate,
              totalFrames,
              camera.radius,
              targetRadius,
              Animation.ANIMATIONLOOPMODE_CONSTANT,
              ease
            )
            
            console.log('[ModelViewer] Camera framed to model:', { center, targetRadius, maxDimension })
          }
        }

        setLoadingProgress(100)
        setShowModel(true)
        setModelReady(true)
        setIsLoading(false)
      } catch (error) {
        if (isDisposedRef.current) return
        const errorMessage = error instanceof Error ? error.message : 'Неизвестная ошибка'
        console.error('[ModelViewer] Error loading model:', errorMessage)
        const message = `Ошибка при загрузке модели: ${errorMessage}`
        onError?.(message)
        setIsLoading(false)
        setModelReady(false)
      }
    }

    loadModel()
  }, [modelPath, onModelLoaded, onError])

  useEffect(() => {
    if (!highlightAllSensors || focusSensorId || !modelReady) {
      setAllSensorsOverlayCircles([])
      return
    }

    const scene = sceneRef.current
    const engine = engineRef.current
    if (!scene || !engine) {
      setAllSensorsOverlayCircles([])
      return
    }

    const allMeshes = importedMeshesRef.current || []
    const sensorMeshes = collectSensorMeshes(allMeshes)
    if (sensorMeshes.length === 0) {
      setAllSensorsOverlayCircles([])
      return
    }

    const engineTyped = engine as Engine
    const updateCircles = () => {
      const circles = computeSensorOverlayCircles({
        scene,
        engine: engineTyped,
        meshes: sensorMeshes,
        sensorStatusMap,
      })
      setAllSensorsOverlayCircles(circles)
    }

    updateCircles()
    const observer = scene.onBeforeRenderObservable.add(updateCircles)
    return () => {
      scene.onBeforeRenderObservable.remove(observer)
      setAllSensorsOverlayCircles([])
    }
  }, [highlightAllSensors, focusSensorId, modelReady, sensorStatusMap])

  useEffect(() => {
    if (!highlightAllSensors || focusSensorId || !modelReady) {
      return
    }

    const scene = sceneRef.current
    if (!scene) return

    const allMeshes = importedMeshesRef.current || []
    if (allMeshes.length === 0) {
      return
    }

    const sensorMeshes = collectSensorMeshes(allMeshes)
    
    console.log('[ModelViewer] Total meshes in model:', allMeshes.length)
    console.log('[ModelViewer] Sensor meshes found:', sensorMeshes.length)
    
    // Log first 5 sensor IDs found in meshes
    const sensorIds = sensorMeshes.map(m => getSensorIdFromMesh(m)).filter(Boolean).slice(0, 5)
    console.log('[ModelViewer] Sample sensor IDs from meshes:', sensorIds)

    if (sensorMeshes.length === 0) {
      console.warn('[ModelViewer] No sensor meshes found in 3D model!')
      return
    }

    applyHighlightToMeshes(
      highlightLayerRef.current,
      highlightedMeshesRef,
      sensorMeshes,
      mesh => {
        const sid = getSensorIdFromMesh(mesh)
        const status = sid ? sensorStatusMap?.[sid] : undefined
        return statusToColor3(status ?? null)
      },
    )
  }, [highlightAllSensors, focusSensorId, modelReady, sensorStatusMap])

  useEffect(() => {
    if (!focusSensorId || !modelReady) {
      for (const m of highlightedMeshesRef.current) { m.renderingGroupId = 0 }
      highlightedMeshesRef.current = []
      highlightLayerRef.current?.removeAllMeshes()
      chosenMeshRef.current = null
      setOverlayPos(null)
      setOverlayData(null)
      setAllSensorsOverlayCircles([])
      return
    }

    const sensorId = (focusSensorId ?? '').trim()
    if (!sensorId) {
      for (const m of highlightedMeshesRef.current) { m.renderingGroupId = 0 }
      highlightedMeshesRef.current = []
      highlightLayerRef.current?.removeAllMeshes()
      chosenMeshRef.current = null
      setOverlayPos(null)
      setOverlayData(null)
      return
     }
 
    const allMeshes = importedMeshesRef.current || []
     
    if (allMeshes.length === 0) {
      for (const m of highlightedMeshesRef.current) { m.renderingGroupId = 0 }
      highlightedMeshesRef.current = []
      highlightLayerRef.current?.removeAllMeshes()
      chosenMeshRef.current = null
      setOverlayPos(null)
      setOverlayData(null)
      return
     }
    
    const sensorMeshes = collectSensorMeshes(allMeshes)
    const allSensorIds = sensorMeshes.map(m => getSensorIdFromMesh(m))
    const chosen = sensorMeshes.find(m => getSensorIdFromMesh(m) === sensorId)

    console.log('[ModelViewer] Sensor focus', {
      requested: sensorId,
      totalImportedMeshes: allMeshes.length,
      totalSensorMeshes: sensorMeshes.length,
      allSensorIds: allSensorIds,
      chosen: chosen ? { id: chosen.id, name: chosen.name, uniqueId: chosen.uniqueId, parent: chosen.parent?.name } : null,
      source: 'result.meshes',
    })

    const scene = sceneRef.current!

    if (chosen) {
      try {
        const camera = scene.activeCamera as ArcRotateCamera
        const bbox = (typeof chosen.getHierarchyBoundingVectors === 'function')
          ? chosen.getHierarchyBoundingVectors()
          : { min: chosen.getBoundingInfo().boundingBox.minimumWorld, max: chosen.getBoundingInfo().boundingBox.maximumWorld }
        const center = bbox.min.add(bbox.max).scale(0.5)
        const size = bbox.max.subtract(bbox.min)
        const maxDimension = Math.max(size.x, size.y, size.z)
        const targetRadius = Math.max(camera.lowerRadiusLimit ?? 2, maxDimension * 1.5)

        // Простое позиционирование камеры - всегда поворачиваемся к датчику
        console.log('[ModelViewer] Calculating camera direction to sensor')
        
        // Вычисляем направление от текущей позиции камеры к датчику
        const directionToSensor = center.subtract(camera.position).normalize()
        
        // Преобразуем в сферические координаты
        // alpha - горизонтальный угол (вокруг оси Y)
        let targetAlpha = Math.atan2(directionToSensor.x, directionToSensor.z)
        
        // beta - вертикальный угол (от вертикали)
        // Используем оптимальный угол 60° для обзора
        let targetBeta = Math.PI / 3 // 60°
        
        console.log('[ModelViewer] Calculated camera direction:', { 
          alpha: (targetAlpha * 180 / Math.PI).toFixed(1) + '°', 
          beta: (targetBeta * 180 / Math.PI).toFixed(1) + '°',
          sensorPosition: { x: center.x.toFixed(2), y: center.y.toFixed(2), z: center.z.toFixed(2) },
          cameraPosition: { x: camera.position.x.toFixed(2), y: camera.position.y.toFixed(2), z: camera.position.z.toFixed(2) }
        })
        
        // Нормализуем alpha в диапазон [-PI, PI]
        while (targetAlpha > Math.PI) targetAlpha -= 2 * Math.PI
        while (targetAlpha < -Math.PI) targetAlpha += 2 * Math.PI
        
        // Ограничиваем beta в разумных пределах
        targetBeta = Math.max(0.1, Math.min(Math.PI - 0.1, targetBeta))

        scene.stopAnimation(camera)

        // Логирование перед анимацией
        console.log('[ModelViewer] Starting camera animation:', {
          sensorId,
          from: {
            target: { x: camera.target.x.toFixed(2), y: camera.target.y.toFixed(2), z: camera.target.z.toFixed(2) },
            radius: camera.radius.toFixed(2),
            alpha: (camera.alpha * 180 / Math.PI).toFixed(1) + '°',
            beta: (camera.beta * 180 / Math.PI).toFixed(1) + '°'
          },
          to: {
            target: { x: center.x.toFixed(2), y: center.y.toFixed(2), z: center.z.toFixed(2) },
            radius: targetRadius.toFixed(2),
            alpha: (targetAlpha * 180 / Math.PI).toFixed(1) + '°',
            beta: (targetBeta * 180 / Math.PI).toFixed(1) + '°'
          },
          alphaChange: ((targetAlpha - camera.alpha) * 180 / Math.PI).toFixed(1) + '°',
          betaChange: ((targetBeta - camera.beta) * 180 / Math.PI).toFixed(1) + '°'
        })

        const ease = new CubicEase()
        ease.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT)
        const frameRate = 60
        const durationMs = 800
        const totalFrames = Math.round((durationMs / 1000) * frameRate)

        Animation.CreateAndStartAnimation('camTarget', camera, 'target', frameRate, totalFrames, camera.target.clone(), center.clone(), Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
        Animation.CreateAndStartAnimation('camRadius', camera, 'radius', frameRate, totalFrames, camera.radius, targetRadius, Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
        Animation.CreateAndStartAnimation('camAlpha', camera, 'alpha', frameRate, totalFrames, camera.alpha, targetAlpha, Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
        Animation.CreateAndStartAnimation('camBeta', camera, 'beta', frameRate, totalFrames, camera.beta, targetBeta, Animation.ANIMATIONLOOPMODE_CONSTANT, ease)

        applyHighlightToMeshes(
          highlightLayerRef.current,
          highlightedMeshesRef,
          [chosen],
          mesh => {
            const sid = getSensorIdFromMesh(mesh)
            const status = sid ? sensorStatusMap?.[sid] : undefined
            return statusToColor3(status ?? null)
          },
        )
        chosenMeshRef.current = chosen
        setOverlayData({ name: chosen.name, sensorId })
      } catch {
        for (const m of highlightedMeshesRef.current) { m.renderingGroupId = 0 }
        highlightedMeshesRef.current = []
        highlightLayerRef.current?.removeAllMeshes()
        chosenMeshRef.current = null
        setOverlayPos(null)
        setOverlayData(null)
       }
     } else {
        for (const m of highlightedMeshesRef.current) { m.renderingGroupId = 0 }
        highlightedMeshesRef.current = []
        highlightLayerRef.current?.removeAllMeshes()
        chosenMeshRef.current = null
        setOverlayPos(null)
        setOverlayData(null)
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [focusSensorId, modelReady, highlightAllSensors])

  useEffect(() => {
    const scene = sceneRef.current
    if (!scene || !modelReady || !isSensorSelectionEnabled) return

    const pickObserver = scene.onPointerObservable.add((pointerInfo: PointerInfo) => {
      if (pointerInfo.type !== PointerEventTypes.POINTERPICK) return
      const pick = pointerInfo.pickInfo
      if (!pick || !pick.hit) {
        onSensorPick?.(null)
        return
      }

      const pickedMesh = pick.pickedMesh
      const sensorId = getSensorIdFromMesh(pickedMesh)

      if (sensorId) {
        onSensorPick?.(sensorId)
      } else {
        onSensorPick?.(null)
      }
    })

    return () => {
      scene.onPointerObservable.remove(pickObserver)
    }
  }, [modelReady, isSensorSelectionEnabled, onSensorPick])

  const computeOverlayPosition = React.useCallback((mesh: AbstractMesh | null) => {
    if (!sceneRef.current || !mesh) return null
    const scene = sceneRef.current
    try {
      const bbox = (typeof mesh.getHierarchyBoundingVectors === 'function')
        ? mesh.getHierarchyBoundingVectors()
        : { min: mesh.getBoundingInfo().boundingBox.minimumWorld, max: mesh.getBoundingInfo().boundingBox.maximumWorld }
      const center = bbox.min.add(bbox.max).scale(0.5)

      const viewport = scene.activeCamera?.viewport.toGlobal(engineRef.current!.getRenderWidth(), engineRef.current!.getRenderHeight())
      if (!viewport) return null

      const projected = Vector3.Project(center, Matrix.Identity(), scene.getTransformMatrix(), viewport)
      if (!projected) return null

      return { left: projected.x, top: projected.y }
    } catch (error) {
      console.error('[ModelViewer] Error computing overlay position:', error)
      return null
    }
  }, [])

  useEffect(() => {
    if (!chosenMeshRef.current || !overlayData) return
    const pos = computeOverlayPosition(chosenMeshRef.current)
    setOverlayPos(pos)
  }, [overlayData, computeOverlayPosition])

  useEffect(() => {
    if (!sceneRef.current || !chosenMeshRef.current || !overlayData) return
    const scene = sceneRef.current
 
    const updateOverlayPosition = () => {
      const pos = computeOverlayPosition(chosenMeshRef.current)
      setOverlayPos(pos)
    }
    scene.registerBeforeRender(updateOverlayPosition)
    return () => scene.unregisterBeforeRender(updateOverlayPosition)
  }, [overlayData, computeOverlayPosition])

  return (
    <div className="w-full h-screen relative bg-gray-900 overflow-hidden">
      {!modelPath ? (
        <div className="h-full flex items-center justify-center">
          <div className="text-center p-8 bg-[#161824] rounded-lg border border-gray-700 max-w-md shadow-xl">
            <div className="text-amber-400 text-lg font-semibold mb-2">
              3D модель не выбрана
            </div>
            <div className="text-gray-300 mb-4">
              Выберите модель в панели «Зоны мониторинга», чтобы начать просмотр
            </div>
            <div className="text-sm text-gray-400">
              Если список пуст, добавьте файлы в каталог assets/big-models или проверьте API
            </div>
          </div>
        </div>
      ) : (
        <>
          <canvas
            ref={canvasRef}
            className={`w-full h-full outline-none block transition-opacity duration-500 ${
              showModel && !webglError ? 'opacity-100' : 'opacity-0'
            }`}
          />
          {webglError ? (
            <div className="absolute inset-0 bg-gray-900 flex items-center justify-center z-50">
              <div className="text-center p-8 bg-[#161824] rounded-lg border border-gray-700 max-w-md shadow-xl">
                <div className="text-red-400 text-lg font-semibold mb-2">
                  3D просмотр недоступен
                </div>
                <div className="text-gray-300 mb-4">
                  {webglError}
                </div>
                <div className="text-sm text-gray-400">
                  Включите аппаратное ускорение в браузере или откройте страницу в другом браузере/устройстве
                </div>
              </div>
            </div>
          ) : isLoading ? (
            <div className="absolute inset-0 bg-gray-900 flex items-center justify-center z-50">
              <LoadingSpinner 
                progress={loadingProgress}
                size={120}
                strokeWidth={8}
              />
            </div>
          ) : !modelReady ? (
            <div className="absolute inset-0 bg-gray-900 flex items-center justify-center z-40">
              <div className="text-center p-8 bg-[#161824] rounded-lg border border-gray-700 max-w-md">
                <div className="text-gray-400 text-lg font-semibold mb-4">
                  3D модель не загружена
                </div>
                <div className="text-sm text-gray-400">
                  Модель не готова к отображению
                </div>
              </div>
            </div>
          ) : null}
          <SceneToolbar 
            onZoomIn={handleZoomIn}
            onZoomOut={handleZoomOut}
            onTopView={handleTopView}
            onPan={handlePan}
            onSelectModel={onSelectModel}
            panActive={panActive}
            onToggleSensorHighlights={useNavigationStore.getState().toggleSensorHighlights}
            sensorHighlightsActive={useNavigationStore.getState().showSensorHighlights}
          />

        </>
      )}
      {/* UPDATED: Interactive overlay circles with hover effects */}
      {allSensorsOverlayCircles.map(circle => {
        const size = 36
        const radius = size / 2
        const fill = hexWithAlpha(circle.colorHex, 0.2)
        const isHovered = hoveredSensorId === circle.sensorId

        return (
          <div
            key={`${circle.sensorId}-${Math.round(circle.left)}-${Math.round(circle.top)}`}
            onClick={() => handleOverlayCircleClick(circle.sensorId)}
            onMouseEnter={() => setHoveredSensorId(circle.sensorId)}
            onMouseLeave={() => setHoveredSensorId(null)}
            style={{
              position: 'absolute',
              left: circle.left - radius,
              top: circle.top - radius,
              width: size,
              height: size,
              borderRadius: '9999px',
              border: `2px solid ${circle.colorHex}`,
              backgroundColor: fill,
              pointerEvents: 'auto',
              cursor: 'pointer',
              transition: 'all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)',
              transform: isHovered ? 'scale(1.4)' : 'scale(1)',
              boxShadow: isHovered 
                ? `0 0 25px ${circle.colorHex}, inset 0 0 10px ${circle.colorHex}` 
                : `0 0 8px ${circle.colorHex}`,
              zIndex: isHovered ? 50 : 10,
            }}
            title={`Датчик: ${circle.sensorId}`}
          />
        )
      })}
      {renderOverlay && overlayPos && overlayData
        ? renderOverlay({ anchor: overlayPos, info: overlayData })
        : null
      }
    </div>
  )
}

export default React.memo(ModelViewer)
