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

// Helper function to create timeline labels
function createTimelineLabel(
  scene: Scene,
  labelId: string,
  position: Vector3,
  actualTimeOffset: number
): void {
  // Calculate time conversions
  const actualTimeMs = actualTimeOffset / 1000 // Convert to milliseconds
  const actualTimeSeconds = actualTimeOffset / 1000000 // Convert to seconds
  
  // Format time label based on magnitude (assuming microseconds input)
  let timeLabelText = ''
  if (actualTimeSeconds >= 1) {
    // If >= 1 second, show in seconds
    timeLabelText = `${actualTimeSeconds.toFixed(1)}s`
  } else if (actualTimeMs >= 1) {
    // If >= 1 millisecond, show in milliseconds
    timeLabelText = `${Math.round(actualTimeMs)}ms`
  } else {
    // If < 1 millisecond, show in microseconds
    timeLabelText = `${Math.round(actualTimeOffset)}μs`
  }
  
  // Create label texture
  const labelTexture = new DynamicTexture(`timeLabel_${labelId}`, { width: 80, height: 32 }, scene)
  // Note: hasAlpha property handled by BabylonJS
  labelTexture.drawText(timeLabelText, null, null, '12px Arial', 'white', 'rgba(0,0,0,0.9)', true)
  
  // Create label plane
  const timeLabelPlane = MeshBuilder.CreatePlane(`timeLabelPlane_${labelId}`, { size: 0.5 }, scene)
  timeLabelPlane.position = position
  
  // Create and apply material
  const timeLabelMaterial = new StandardMaterial(`timeLabelMaterial_${labelId}`, scene)
  timeLabelMaterial.diffuseTexture = labelTexture
  timeLabelMaterial.useAlphaFromDiffuseTexture = true
  timeLabelMaterial.backFaceCulling = false
  timeLabelPlane.material = timeLabelMaterial
}

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 ScreenshotEvent {
  timestamp: number
  screenshot: string // base64 image data
  index: number
}

interface BabylonTimelineViewerProps {
  width?: number
  height?: number
  httpRequests?: HTTPRequest[]
  screenshots?: ScreenshotEvent[]
}

export default function BabylonTimelineViewer({ width = 800, height = 600, httpRequests = [], screenshots = [] }: BabylonTimelineViewerProps) {
  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

    // Create universal camera for enhanced keyboard and mouse navigation
    const camera = new UniversalCamera('camera1', new Vector3(0, 5, -10), scene)
    camera.attachControl(canvas, true)
    
    // Set camera target to look at the origin
    camera.setTarget(new Vector3(0, 0, 0))
    
    // Configure camera movement speed and sensitivity
    camera.speed = 0.5
    camera.angularSensibility = 2000
    
    // Enable WASD keys explicitly
    camera.keysUp = [87] // W key
    camera.keysDown = [83] // S key
    camera.keysLeft = [65] // A key  
    camera.keysRight = [68] // D key
    
    scene.activeCamera = camera

    const light = new HemisphericLight('light', new Vector3(-5, 3, -1), scene)
    const light2 = new HemisphericLight('light', new Vector3(5, 3, -1), scene)
    light.intensity = 0.9
    light2.intensity = 0.9

    // Create ground plane oriented along the Z-axis timeline
    /*const ground = MeshBuilder.CreateGround('ground', { width: 20, height: 12 }, scene)
    ground.rotation.x = -Math.PI / 2 // Rotate to lie along Z-axis
    ground.position = new Vector3(0, 0, 5) // Center at middle of timeline
    const groundMaterial = new StandardMaterial('groundMaterial', scene)
    groundMaterial.diffuseColor = new Color3(0.2, 0.2, 0.2)
    groundMaterial.alpha = 0.3
    ground.material = groundMaterial
*/
    // Create timeline start marker at Z = 0
    const startMarker = MeshBuilder.CreateSphere('timelineStart', { diameter: 0.3 }, scene)
    startMarker.position = new Vector3(0, 0, 0)
    const startMaterial = new StandardMaterial('startMaterial', scene)
    startMaterial.diffuseColor = new Color3(0.2, 0.8, 0.2) // Green for start
    startMaterial.specularColor = new Color3(0.5, 1, 0.5)
    startMarker.material = startMaterial

    // Create timeline end marker at actual completion position if we have requests
    if (httpRequests && httpRequests.length > 0) {
      // Find the actual completion time of the last finishing request
      const endTimes = httpRequests.map(req => (req.timing.startOffset || 0) + (req.timing.duration || 0))
      const minStartTime = Math.min(...httpRequests.map(req => req.timing.startOffset || 0))
      const actualMaxEndTime = Math.max(...endTimes)
      const totalTimeRange = actualMaxEndTime - minStartTime
      
      // Calculate the Z position for the actual end time
      const normalizedEndTime = totalTimeRange > 0 ? (actualMaxEndTime - minStartTime) / totalTimeRange : 1
      const actualEndZ = 0 + (normalizedEndTime * (20 - 0)) // Using minZ=0, maxZ=20
      
      const endMarker = MeshBuilder.CreateSphere('timelineEnd', { diameter: 0.3 }, scene)
      endMarker.position = new Vector3(0, 0, actualEndZ)
      const endMaterial = new StandardMaterial('endMaterial', scene)
      endMaterial.diffuseColor = new Color3(0.8, 0.2, 0.2) // Red for end
      endMaterial.specularColor = new Color3(1, 0.5, 0.5)
      endMarker.material = endMaterial
    } else {
      // Fallback to Z = 20 if no requests
      const endMarker = MeshBuilder.CreateSphere('timelineEnd', { diameter: 0.3 }, scene)
      endMarker.position = new Vector3(0, 0, 20)
      const endMaterial = new StandardMaterial('endMaterial', scene)
      endMaterial.diffuseColor = new Color3(0.8, 0.2, 0.2) // Red for end
      endMaterial.specularColor = new Color3(1, 0.5, 0.5)
      endMarker.material = endMaterial
    }


    // Visualize HTTP requests in timeline swimlanes 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 Z-axis timeline normalization
      const startTimes = httpRequests.map(req => req.timing.startOffset || 0)
      const endTimes = httpRequests.map(req => (req.timing.startOffset || 0) + (req.timing.duration || 0))
      const minStartTime = Math.min(...startTimes)
      const maxEndTime = Math.max(...endTimes)
      const totalTimeRange = maxEndTime - minStartTime
      
      // Debug: Check the actual timing values (assuming microseconds)
      console.log(`Timeline debug: minStart=${minStartTime}μs, maxEnd=${maxEndTime}μs, totalRange=${totalTimeRange}μs`)
      console.log(`Timeline debug: totalRange in ms=${totalTimeRange/1000}ms, in seconds=${totalTimeRange/1000000}s`)
      console.log(`Sample request timing:`, httpRequests[0]?.timing)

      // 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())
      
      // Sort hostnames by their earliest request start time
      const hostnamesWithStartTimes = hostnames.map(hostname => {
        const requests = requestsByHostname.get(hostname)!
        const earliestStartTime = Math.min(...requests.map(req => req.timing.startOffset || 0))
        return { hostname, earliestStartTime }
      })
      hostnamesWithStartTimes.sort((a, b) => a.earliestStartTime - b.earliestStartTime)
      
      const hostCount = hostnames.length
      const minZ = 0 // Timeline start at Z = 0
      const maxZ = 20 // Timeline end at Z = 10
      const minHeight = 0.1 // Minimum box height (0.1 meters)
      const maxHeight = 2 // Maximum box height (5 meters)
      const swimlaneSpacing = 1.5 // Spacing between swimlanes on X-axis

      // Calculate the full width needed to span all swimlanes
      const totalWidth = (hostCount - 1) * swimlaneSpacing + 4 // Add padding on both sides
      
      // Create timeline grid lines at regular intervals across the normalized 0-10 timeline
      const gridLineCount = 10 // Create 10 grid lines across the timeline
      for (let i = 0; i <= gridLineCount; i++) {
        const zPosition = minZ + (i / gridLineCount) * (maxZ - minZ) // Evenly spaced from 0 to 10
        
        // Calculate the actual time this position represents
        const actualTimeOffset = (i / gridLineCount) * totalTimeRange // This is in microseconds
        const actualTimeMs = actualTimeOffset / 1000 // Convert to milliseconds
        const actualTimeSeconds = actualTimeOffset / 1000000 // Convert to seconds
        
        // Debug: Let's see what the actual values are
        console.log(`Grid line ${i}: offset=${actualTimeOffset}μs, ms=${actualTimeMs}ms, seconds=${actualTimeSeconds}s, totalRange=${totalTimeRange}μs`)
        
        // Create grid line spanning the full width of all content
        const linePoints = [
          new Vector3(-totalWidth / 2, 0, zPosition),
          new Vector3(totalWidth / 2, 0, zPosition)
        ]
        
        MeshBuilder.CreateLines(`gridLine_${i}`, { points: linePoints }, scene)
        const gridMaterial = new StandardMaterial(`gridMaterial_${i}`, scene)
        gridMaterial.diffuseColor = new Color3(0.5, 0.5, 0.5)
        gridMaterial.alpha = 0.3
        
        // Add timeline label for each grid line
        createTimelineLabel(scene, i.toString(), new Vector3(-0.5, -0.5, zPosition), actualTimeOffset)
      }

      // Create filmstrip planes for screenshot events
      if (screenshots.length > 0) {
        screenshots.forEach((screenshot, index) => {
          // Calculate Z position for this screenshot timestamp using the same normalization as HTTP requests
          const normalizedScreenshotTime = totalTimeRange > 0 ? (screenshot.timestamp - minStartTime) / totalTimeRange : 0
          const screenshotZ = minZ + (normalizedScreenshotTime * (maxZ - minZ))
          

          const screenshotPlane = MeshBuilder.CreatePlane(`screenshotPlane_${index}`, { 
            width: 2,
            height: 2
          }, scene)
          
          // Position the plane at x:0 y:1
          screenshotPlane.position = new Vector3(0, 1, screenshotZ)
          screenshotPlane.rotation.y = Math.PI / 2 // Rotate to face along timeline
          
          // Create semi-transparent material
          const screenshotMaterial = new StandardMaterial(`screenshotMaterial_${index}`, scene)
          screenshotMaterial.diffuseColor = new Color3(1, 1, 1) // White plane
          screenshotMaterial.alpha = 0.5 // 50% opacity
          screenshotMaterial.backFaceCulling = false // Visible from both sides
          
          screenshotPlane.material = screenshotMaterial
        })
      }

      hostnamesWithStartTimes.forEach(({ hostname }, sortedIndex) => {
        // Calculate X position alternating left/right from origin, with distance increasing by start time order
        // Index 0 (earliest) -> X = 0, Index 1 -> X = -2, Index 2 -> X = 2, Index 3 -> X = -4, Index 4 -> X = 4, etc.
        let xPosition = 0
        if (sortedIndex > 0) {
          const distance = Math.ceil(sortedIndex / 2) * swimlaneSpacing
          xPosition = (sortedIndex % 2 === 1) ? -distance : distance
        }
        
        // Create hostname label at the start of each swimlane
        const labelTexture = new DynamicTexture(`hostLabel_${sortedIndex}`, { 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_${sortedIndex}`, { size: 1.5 }, scene)
        hostLabel.position = new Vector3(xPosition, -1, -1) // Position at Y = -1, Z = -1 (below ground, before timeline start)
        
        const labelMaterial = new StandardMaterial(`hostLabelMaterial_${sortedIndex}`, 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<{
          startZ: number
          endZ: number
          height: number
          yPosition: number
        }> = []

        // Create boxes for each request from this hostname
        hostnameRequests.forEach((request, requestIndex) => {
          // Calculate Z positions based on timeline (start time to end time)
          const startTime = request.timing.startOffset || 0
          const duration = request.timing.duration || 1000 // Default 1ms if no duration
          const endTime = startTime + duration
          
          // Normalize to Z-axis range (0 to 10)
          const normalizedStartTime = totalTimeRange > 0 ? (startTime - minStartTime) / totalTimeRange : 0
          const normalizedEndTime = totalTimeRange > 0 ? (endTime - minStartTime) / totalTimeRange : 0
          
          const startZ = minZ + (normalizedStartTime * (maxZ - minZ))
          const endZ = minZ + (normalizedEndTime * (maxZ - minZ))
          
          // Calculate box depth (duration) and center Z position
          const boxDepth = Math.max(0.05, endZ - startZ) // Minimum depth of 0.05m
          
          // 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 in this swimlane
          let stackHeight = 0
          for (const placedBox of placedBoxes) {
            // Check if this request overlaps with a placed box on the timeline (Z-axis)
            if (startZ < placedBox.endZ && endZ > placedBox.startZ) {
              // 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)
          
          // Calculate server latency and network time portions
          const serverLatency = request.timing.serverLatency || 0
          const networkTime = duration - serverLatency
          
          // Calculate proportional depths for server vs network time
          const serverLatencyRatio = serverLatency > 0 ? serverLatency / duration : 0
          const networkTimeRatio = networkTime > 0 ? networkTime / duration : 1
          
          const serverDepth = boxDepth * serverLatencyRatio
          const networkDepth = boxDepth * networkTimeRatio
          
          // Calculate color based on vertical position (same gradient for both boxes)
          const verticalColorIntensity = Math.min(1.0, Math.max(0.3, (stackHeight + boxHeight) / 8.0))
          
          // Create server time box (gray) - positioned at the start of the request
          if (serverDepth > 0.01) { // Only create if server time is significant
            const serverCenterZ = startZ + (serverDepth / 2)
            const serverBox = MeshBuilder.CreateBox(`serverBox_${sortedIndex}_${requestIndex}`, {
              width: 0.2,
              height: boxHeight,
              depth: serverDepth
            }, scene)
            
            serverBox.position = new Vector3(xPosition, yPosition, serverCenterZ)
            
            // Gray material with gradient and 50% opacity
            const serverMaterial = new StandardMaterial(`serverMaterial_${sortedIndex}_${requestIndex}`, scene)
            serverMaterial.diffuseColor = new Color3(verticalColorIntensity * 0.5, verticalColorIntensity * 0.5, verticalColorIntensity * 0.5)
            serverMaterial.alpha = 0.5 // 50% opacity for server time boxes
            
            if (request.fromCache) {
              serverMaterial.alpha = 0.35 // Reduce further for cached requests (0.5 * 0.7)
            }
            
            serverBox.material = serverMaterial
          }
          
          // Create network time box (blue) - positioned after server time
          const networkStartZ = startZ + serverDepth
          const networkCenterZ = networkStartZ + (networkDepth / 2)
          
          const networkBox = MeshBuilder.CreateBox(`networkBox_${sortedIndex}_${requestIndex}`, {
            width: 0.2,
            height: boxHeight,
            depth: networkDepth
          }, scene)
          
          networkBox.position = new Vector3(xPosition, yPosition, networkCenterZ)

          // Add this box to the placed boxes list for future overlap checks
          placedBoxes.push({
            startZ,
            endZ,
            height: boxHeight,
            yPosition: stackHeight + boxHeight
          })
          
          // Blue material with gradient and status code coloring
          const networkMaterial = new StandardMaterial(`networkMaterial_${sortedIndex}_${requestIndex}`, scene)
          if (request.statusCode) {
            if (request.statusCode >= 200 && request.statusCode < 300) {
              // Blue with gradient for success
              networkMaterial.diffuseColor = new Color3(0, 0, verticalColorIntensity)
            } else if (request.statusCode >= 300 && request.statusCode < 400) {
              networkMaterial.diffuseColor = new Color3(1, 1, 0) // Yellow for redirects
            } else if (request.statusCode >= 400 && request.statusCode < 500) {
              networkMaterial.diffuseColor = new Color3(1, 0.5, 0) // Orange for client errors
            } else if (request.statusCode >= 500) {
              networkMaterial.diffuseColor = new Color3(1, 0, 0) // Red for server errors
            }
          } else {
            networkMaterial.diffuseColor = new Color3(0, 0, 0.5) // Dark blue for unknown
          }
          
          // Add transparency for cached requests
          if (request.fromCache) {
            networkMaterial.alpha = 0.7
          }
          
          networkBox.material = networkMaterial
          
          // Add yellow queue time line if queue time exists
          if (request.timing.queueTime && request.timing.queueTime > 0) {
            // Calculate queue start time (when request was first queued - before the actual request start)
            const queueStartTime = startTime - request.timing.queueTime
            const queueEndTime = startTime // Queue ends when actual request processing starts
            
            // Normalize queue start and end times to Z-axis
            const normalizedQueueStartTime = totalTimeRange > 0 ? Math.max(0, (queueStartTime - minStartTime) / totalTimeRange) : 0
            const normalizedQueueEndTime = totalTimeRange > 0 ? (queueEndTime - minStartTime) / totalTimeRange : 0
            
            const queueStartZ = minZ + (normalizedQueueStartTime * (maxZ - minZ))
            const queueEndZ = minZ + (normalizedQueueEndTime * (maxZ - minZ))
            
            // Calculate queue line depth (from queue start to queue end)
            const queueLineDepth = Math.max(0.01, queueEndZ - queueStartZ) // Minimum depth of 0.01m
            const queueLineCenterZ = queueStartZ + (queueLineDepth / 2)
            
            // Create yellow queue time line positioned slightly above the request box
            const queueLine = MeshBuilder.CreateBox(`queueLine_${sortedIndex}_${requestIndex}`, {
              width: 0.05, // Thin line width
              height: 0.05, // Thin line height  
              depth: queueLineDepth
            }, scene)
            
            // Position queue line at the same vertical center as the request box
            queueLine.position = new Vector3(xPosition, yPosition, queueLineCenterZ)
            
            // Create yellow material for queue time with 25% opacity
            const queueMaterial = new StandardMaterial(`queueMaterial_${sortedIndex}_${requestIndex}`, scene)
            queueMaterial.diffuseColor = new Color3(1, 1, 0) // Yellow color
            queueMaterial.emissiveColor = new Color3(0.2, 0.2, 0) // Slight glow
            queueMaterial.alpha = 0.25 // 25% opacity for queue time boxes
            queueLine.material = queueMaterial
          }
        })
        
        // Create a swimlane line from timeline start to end for visual reference
        const swimlaneLinePoints = [new Vector3(xPosition, 0, minZ), new Vector3(xPosition, 0, maxZ)]
        MeshBuilder.CreateLines(`swimlaneLine_${sortedIndex}`, { points: swimlaneLinePoints }, scene)
        const lineMaterial = new StandardMaterial(`swimlaneLineMaterial_${sortedIndex}`, scene)
        lineMaterial.diffuseColor = new Color3(0.4, 0.4, 0.4)
        lineMaterial.alpha = 0.3
      })
    }

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

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

    window.addEventListener('resize', handleResize)

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

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