import { useEffect, useRef } from 'react'
import {
  Engine,
  Scene,
  ArcRotateCamera,
  Vector3,
  HemisphericLight,
  MeshBuilder,
  StandardMaterial,
  Color3,
  DynamicTexture
} from 'babylonjs'

interface HTTPRequest {
  requestId: string
  url: string
  hostname: string
  method: string
  resourceType: string
  priority: string
  statusCode?: number
  mimeType?: string
  protocol?: string
  timing: {
    start: number
    startOffset?: number
    end?: number
    duration?: number
    queueTime?: number
    serverLatency?: number
  }
  encodedDataLength?: number
  contentLength?: number
  fromCache: boolean
  connectionReused: boolean
}

interface BabylonViewerProps {
  width?: number
  height?: number
  httpRequests?: HTTPRequest[]
}

export default function BabylonViewer({ width = 800, height = 600, httpRequests = [] }: BabylonViewerProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const engineRef = useRef<Engine | null>(null)
  const sceneRef = useRef<Scene | null>(null)

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

    const canvas = canvasRef.current
    const engine = new Engine(canvas, true)
    engineRef.current = engine

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

    const camera = new ArcRotateCamera('camera1', -Math.PI / 2, Math.PI / 2.5, 25, Vector3.Zero(), scene)
    camera.attachControl(canvas, true)
    scene.activeCamera = camera

    const light = new HemisphericLight('light', new Vector3(0, 1, 0), scene)
    light.intensity = 0.7

    // Create ground plane for reference
    const ground = MeshBuilder.CreateGround('ground', { width: 50, height: 30 }, scene)
    const groundMaterial = new StandardMaterial('groundMaterial', scene)
    groundMaterial.diffuseColor = new Color3(0.2, 0.2, 0.2)
    groundMaterial.alpha = 0.3
    ground.material = groundMaterial

    // Create central sphere at origin
    const centralSphere = MeshBuilder.CreateSphere('centralSphere', { diameter: 0.5 }, scene)
    centralSphere.position = Vector3.Zero()
    const sphereMaterial = new StandardMaterial('sphereMaterial', scene)
    sphereMaterial.diffuseColor = new Color3(0.3, 0.3, 0.8) // Blue sphere
    sphereMaterial.specularColor = new Color3(0.5, 0.5, 1)
    centralSphere.material = sphereMaterial

    // Visualize HTTP requests as 3D objects arranged radially by hostname
    if (httpRequests && httpRequests.length > 0) {
      // Group requests by hostname
      const requestsByHostname = new Map<string, HTTPRequest[]>()
      httpRequests.forEach(request => {
        if (!requestsByHostname.has(request.hostname)) {
          requestsByHostname.set(request.hostname, [])
        }
        requestsByHostname.get(request.hostname)!.push(request)
      })

      // Find min and max start times for normalization
      const startTimes = httpRequests.map(req => req.timing.startOffset || 0)
      const minStartTime = Math.min(...startTimes)
      const maxStartTime = Math.max(...startTimes)
      const timeRange = maxStartTime - minStartTime

      // Find min and max content-length values for height normalization
      const contentLengths = httpRequests
        .map(req => req.contentLength || 0)
        .filter(size => size > 0) // Only consider requests with content-length
      const minContentLength = contentLengths.length > 0 ? Math.min(...contentLengths) : 1
      const maxContentLength = contentLengths.length > 0 ? Math.max(...contentLengths) : 1
      const contentLengthRange = maxContentLength - minContentLength


      const hostnames = Array.from(requestsByHostname.keys())
      const hostCount = hostnames.length
      const labelRadius = 12 // Distance from center for hostname labels
      const minDistance = 1 // Minimum distance from center (1 meter)
      const maxDistance = 10 // Maximum distance from center (10 meters)
      const minHeight = 0.1 // Minimum box height (0.1 meters)
      const maxHeight = 5 // Maximum box height (5 meters)
      const minDepth = 0.05 // Minimum box depth (0.05 meters)

      hostnames.forEach((hostname, hostIndex) => {
        // Calculate radial position for this hostname
        const angle = (hostIndex / hostCount) * 2 * Math.PI
        const labelX = labelRadius * Math.cos(angle)
        const labelZ = labelRadius * Math.sin(angle)
        
        // Create hostname label that always faces camera
        const labelTexture = new DynamicTexture(`hostLabel_${hostIndex}`, { width: 256, height: 64 }, scene)
        // Note: hasAlpha property handled by BabylonJS
        labelTexture.drawText(
          hostname,
          null, null,
          '16px Arial',
          'white',
          'rgba(0,0,0,0.7)',
          true
        )
        
        const hostLabel = MeshBuilder.CreatePlane(`hostLabelPlane_${hostIndex}`, { size: 1.5 }, scene)
        hostLabel.position = new Vector3(labelX, 1.5, labelZ)
        
        // Rotate label to face origin on vertical axis only, then add 180 degree rotation
        const labelDirectionToOrigin = Vector3.Zero().subtract(hostLabel.position).normalize()
        const labelYRotation = Math.atan2(labelDirectionToOrigin.x, labelDirectionToOrigin.z)
        hostLabel.rotation.y = labelYRotation + Math.PI // Add 180 degrees (π radians)
        
        const labelMaterial = new StandardMaterial(`hostLabelMaterial_${hostIndex}`, scene)
        labelMaterial.diffuseTexture = labelTexture
        labelMaterial.useAlphaFromDiffuseTexture = true
        labelMaterial.backFaceCulling = false
        hostLabel.material = labelMaterial

        // Get requests for this hostname and sort by start time
        const hostnameRequests = requestsByHostname.get(hostname)!
        hostnameRequests.sort((a, b) => (a.timing.startOffset || 0) - (b.timing.startOffset || 0))
        
        // Track placed boxes for this hostname to handle stacking
        const placedBoxes: Array<{
          startDistance: number
          endDistance: number
          height: number
          yPosition: number
        }> = []

        // Create boxes for each request from this hostname
        hostnameRequests.forEach((request, requestIndex) => {
          // Calculate start and end distances from center based on timing
          const startTime = request.timing.startOffset || 0
          const duration = request.timing.duration || 1000 // Default 1ms if no duration
          const endTime = startTime + duration
          
          const normalizedStartTime = timeRange > 0 ? (startTime - minStartTime) / timeRange : 0
          const normalizedEndTime = timeRange > 0 ? (endTime - minStartTime) / timeRange : 0
          
          const startDistance = minDistance + (normalizedStartTime * (maxDistance - minDistance))
          const endDistance = minDistance + (normalizedEndTime * (maxDistance - minDistance))
          
          // Calculate box depth and center position
          const boxDepth = Math.max(minDepth, endDistance - startDistance)
          const centerDistance = startDistance + (boxDepth / 2)
          
          // Position box at center distance along the hostname's angle
          const boxX = centerDistance * Math.cos(angle)
          const boxZ = centerDistance * Math.sin(angle)
          
          // Calculate height based on content-length
          const contentLength = request.contentLength || 0
          let boxHeight = minHeight
          if (contentLength > 0 && contentLengthRange > 0) {
            const normalizedSize = (contentLength - minContentLength) / contentLengthRange
            boxHeight = minHeight + (normalizedSize * (maxHeight - minHeight))
          }

          // Find the highest Y position for overlapping boxes
          let stackHeight = 0
          for (const placedBox of placedBoxes) {
            // Check if this request overlaps with a placed box
            if (startDistance < placedBox.endDistance && endDistance > placedBox.startDistance) {
              // This box overlaps, so we need to stack it above
              stackHeight = Math.max(stackHeight, placedBox.yPosition)
            }
          }

          // Calculate Y position (center of the box) - stack height is the bottom of where this box should be
          const yPosition = stackHeight + (boxHeight / 2)
          
          // Create box with duration-based depth and content-length based height
          const box = MeshBuilder.CreateBox(`request_${hostIndex}_${requestIndex}`, {
            width: 0.1,
            height: boxHeight,
            depth: boxDepth
          }, scene)
          
          box.position = new Vector3(boxX, yPosition, boxZ)

          // Add this box to the placed boxes list for future overlap checks
          // yPosition here represents the top of this box for future stacking
          placedBoxes.push({
            startDistance,
            endDistance,
            height: boxHeight,
            yPosition: stackHeight + boxHeight
          })
          
          // Rotate box to face the origin only on the vertical (Y) axis
          const directionToOrigin = Vector3.Zero().subtract(box.position).normalize()
          const yRotation = Math.atan2(directionToOrigin.x, directionToOrigin.z)
          box.rotation.y = yRotation
          
          // Calculate color based on vertical position for success requests
          // Higher boxes are lighter (closer to white), lower boxes are darker
          const verticalColorIntensity = Math.min(1.0, Math.max(0.3, (stackHeight + boxHeight) / 8.0))
          
          // Color based on status code
          const material = new StandardMaterial(`material_${hostIndex}_${requestIndex}`, scene)
          if (request.statusCode) {
            if (request.statusCode >= 200 && request.statusCode < 300) {
              material.diffuseColor = new Color3(verticalColorIntensity, verticalColorIntensity, verticalColorIntensity)
            } else if (request.statusCode >= 300 && request.statusCode < 400) {
              material.diffuseColor = new Color3(1, 1, 0) // Yellow for redirects
            } else if (request.statusCode >= 400 && request.statusCode < 500) {
              material.diffuseColor = new Color3(1, 0.5, 0) // Orange for client errors
            } else if (request.statusCode >= 500) {
              material.diffuseColor = new Color3(1, 0, 0) // Red for server errors
            }
          } else {
            material.diffuseColor = new Color3(0.5, 0.5, 0.5) // Gray for unknown
          }
          
          // Add transparency for cached requests
          if (request.fromCache) {
            material.alpha = 0.7
          }
          
          box.material = material
        })
        
        // Create a line from center to hostname label position for visual connection
        const linePoints = [Vector3.Zero(), new Vector3(labelX * 0.8, 0, labelZ * 0.8)]
        MeshBuilder.CreateLines(`connectionLine_${hostIndex}`, { points: linePoints }, scene)
        const lineMaterial = new StandardMaterial(`lineMaterial_${hostIndex}`, scene)
        lineMaterial.diffuseColor = new Color3(0.4, 0.4, 0.4)
        lineMaterial.alpha = 0.5
      })
    }

    engine.runRenderLoop(() => {
      scene.render()
    })

    const handleResize = () => {
      engine.resize()
    }

    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
      engine.dispose()
    }
  }, [httpRequests])

  return (
    <canvas
      ref={canvasRef}
      width={width}
      height={height}
      style={{ width: '100%', height: '100%', display: 'block' }}
    />
  )
}