'use client'

import { useRef, useEffect, useState, useMemo } from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import * as THREE from 'three'
import { usePlayerStore } from '@/stores/playerStore'
import { useWorldStore } from '@/stores/worldStore'
import { useGameStore } from '@/stores/gameStore'
import { useSettingsStore } from '@/stores/settingsStore'
import { TerrainGenerator } from '@/engine/terrain/generator'
import { buildChunkMesh, setAtlasMap } from '@/engine/voxel/meshBuilder'
import { ChunkMesh } from '@/components/world/ChunkMesh'
import {
  PLAYER_EYE_HEIGHT, CHUNK_WIDTH, CHUNK_DEPTH, BLOCK_AIR,
  BLOCK_DEFS, REACH_DISTANCE, DAY_LENGTH_TICKS, ATLAS_SIZE,
  TILES_PER_ROW, TEXTURE_SIZE, SEA_LEVEL,
} from '@/engine/constants'

// ─────────────────────────────────────────────────────────────
// Shared raycast result ref – other components can import this
// ─────────────────────────────────────────────────────────────
export interface RaycastResult {
  blockPos: [number, number, number]
  placePos: [number, number, number]
  blockId: number
}

export const raycastResultRef: { current: RaycastResult | null } = { current: null }

// ─────────────────────────────────────────────────────────────
// 1. Texture Atlas Loading
// ─────────────────────────────────────────────────────────────
function useTextureAtlas() {
  const [texture, setTexture] = useState<THREE.Texture | null>(null)
  const [loaded, setLoaded] = useState(false)

  useEffect(() => {
    let cancelled = false

    const load = async () => {
      try {
        // Load atlas mapping JSON first
        const res = await fetch('/textures/atlas.json')
        const map = await res.json()
        setAtlasMap(map)

        // Then load the atlas texture image
        const loader = new THREE.TextureLoader()
        loader.load(
          '/textures/atlas.png',
          (tex) => {
            if (cancelled) return
            tex.magFilter = THREE.NearestFilter
            tex.minFilter = THREE.NearestFilter
            tex.wrapS = THREE.ClampToEdgeWrapping
            tex.wrapT = THREE.ClampToEdgeWrapping
            tex.colorSpace = THREE.SRGBColorSpace
            tex.generateMipmaps = false
            setTexture(tex)
            setLoaded(true)
          },
          undefined,
          () => {
            // On error, continue without textures
            if (!cancelled) setLoaded(true)
          }
        )
      } catch {
        if (!cancelled) setLoaded(true)
      }
    }

    load()
    return () => { cancelled = true }
  }, [])

  return { texture, loaded }
}

// ─────────────────────────────────────────────────────────────
// 2. First-Person Camera
// ─────────────────────────────────────────────────────────────
function FirstPersonCamera() {
  const { camera } = useThree()
  const playerRef = useRef(usePlayerStore.getState().player)

  useEffect(() => {
    const unsub = usePlayerStore.subscribe((s) => {
      playerRef.current = s.player
    })
    return unsub
  }, [])

  useFrame(() => {
    const player = playerRef.current
    if (!player) return

    const [px, py, pz] = player.position
    const [yaw, pitch] = player.rotation

    camera.position.set(px, py + PLAYER_EYE_HEIGHT, pz)
    camera.rotation.order = 'YXZ'
    camera.rotation.set(pitch, yaw, 0)
  })

  return null
}

// ─────────────────────────────────────────────────────────────
// 3. World Renderer – chunk loading / unloading around player
// ─────────────────────────────────────────────────────────────
function WorldRenderer({ atlasTexture }: { atlasTexture: THREE.Texture | null }) {
  const terrainGen = useRef<TerrainGenerator | null>(null)
  const loadedChunks = useRef<Set<string>>(new Set())
  const playerRef = useRef(usePlayerStore.getState().player)
  const chunkDataRef = useRef<Map<string, any>>(new Map())
  const [renderChunks, setRenderChunks] = useState<Array<{ key: string; data: any }>>([])

  useEffect(() => {
    const { world } = useWorldStore.getState()
    if (world && !terrainGen.current) {
      terrainGen.current = new TerrainGenerator(world.seed)
    }
    const unsub = usePlayerStore.subscribe((s) => {
      playerRef.current = s.player
    })
    return unsub
  }, [])

  useFrame(() => {
    const player = playerRef.current
    if (!player || !terrainGen.current) return

    const [px, , pz] = player.position
    const pcx = Math.floor(px / CHUNK_WIDTH)
    const pcz = Math.floor(pz / CHUNK_DEPTH)
    const rd = useSettingsStore.getState().renderDistance

    let needsUpdate = false
    const newChunks = new Map(chunkDataRef.current)
    let loadedThisFrame = 0
    const maxPerFrame = 2

    // Spiral-outward ordering for better visual loading
    // Load chunks in concentric rings around the player
    for (let ring = 0; ring <= rd && loadedThisFrame < maxPerFrame; ring++) {
      for (let dx = -ring; dx <= ring && loadedThisFrame < maxPerFrame; dx++) {
        for (let dz = -ring; dz <= ring && loadedThisFrame < maxPerFrame; dz++) {
          // Only process the outer ring
          if (ring > 0 && Math.abs(dx) < ring && Math.abs(dz) < ring) continue

          // Circular render distance check
          if (dx * dx + dz * dz > rd * rd) continue

          const cx = pcx + dx
          const cz = pcz + dz
          const key = `${cx},${cz}`

          if (!loadedChunks.current.has(key)) {
            const chunk = terrainGen.current.generateChunk(cx, cz)
            useWorldStore.getState().setChunk(key, chunk)
            loadedChunks.current.add(key)
            newChunks.set(key, chunk)
            needsUpdate = true
            loadedThisFrame++
          }
        }
      }
    }

    // Unload distant chunks (circular check with 2-chunk buffer)
    const unloadThreshold = (rd + 2) * (rd + 2)
    for (const key of [...loadedChunks.current]) {
      const [cx, cz] = key.split(',').map(Number)
      const dx = cx - pcx
      const dz = cz - pcz
      if (dx * dx + dz * dz > unloadThreshold) {
        loadedChunks.current.delete(key)
        useWorldStore.getState().removeChunk(key)
        newChunks.delete(key)
        needsUpdate = true
      }
    }

    if (needsUpdate) {
      chunkDataRef.current = newChunks
      setRenderChunks(
        Array.from(newChunks.entries()).map(([key, data]) => ({ key, data }))
      )
    }
  })

  return (
    <>
      {renderChunks.map(({ key, data }) => (
        <ChunkMesh key={key} chunk={data} atlasTexture={atlasTexture} />
      ))}
    </>
  )
}

// ─────────────────────────────────────────────────────────────
// 7. Block Highlight – wireframe cube around the targeted block
// ─────────────────────────────────────────────────────────────
function BlockHighlight({ position }: { position: [number, number, number] | null }) {
  const geometry = useMemo(() => new THREE.EdgesGeometry(new THREE.BoxGeometry(1.005, 1.005, 1.005)), [])

  if (!position) return null

  return (
    <lineSegments position={position} geometry={geometry}>
      <lineBasicMaterial
        color="#000000"
        transparent
        opacity={0.6}
        depthTest={false}
      />
    </lineSegments>
  )
}

// ─────────────────────────────────────────────────────────────
// 4. Block Raycaster – DDA voxel raycasting
// ─────────────────────────────────────────────────────────────
function BlockRaycaster() {
  const { camera } = useThree()
  const [highlightPos, setHighlightPos] = useState<[number, number, number] | null>(null)
  const resultRef = useRef<RaycastResult | null>(null)

  useFrame(() => {
    const player = usePlayerStore.getState().player
    if (!player) {
      setHighlightPos(null)
      raycastResultRef.current = null
      return
    }

    const [px, py, pz] = player.position
    const [yaw, pitch] = player.rotation

    // Eye position
    const ox = px
    const oy = py + PLAYER_EYE_HEIGHT
    const oz = pz

    // Direction from yaw/pitch (matches FirstPersonCamera YXZ rotation)
    const dirX = -Math.sin(yaw) * Math.cos(pitch)
    const dirY = -Math.sin(pitch)
    const dirZ = -Math.cos(yaw) * Math.cos(pitch)

    let x = Math.floor(ox)
    let y = Math.floor(oy)
    let z = Math.floor(oz)

    const stepX = dirX >= 0 ? 1 : -1
    const stepY = dirY >= 0 ? 1 : -1
    const stepZ = dirZ >= 0 ? 1 : -1

    // Avoid division by zero with epsilon
    const eps = 1e-10
    const tDeltaX = Math.abs(1 / (dirX || eps))
    const tDeltaY = Math.abs(1 / (dirY || eps))
    const tDeltaZ = Math.abs(1 / (dirZ || eps))

    let tMaxX = dirX >= 0
      ? (x + 1 - ox) / (dirX || eps)
      : (ox - x) / (-dirX || eps)
    let tMaxY = dirY >= 0
      ? (y + 1 - oy) / (dirY || eps)
      : (oy - y) / (-dirY || eps)
    let tMaxZ = dirZ >= 0
      ? (z + 1 - oz) / (dirZ || eps)
      : (oz - z) / (-dirZ || eps)

    let prevX = x, prevY = y, prevZ = z
    const maxDist = REACH_DISTANCE
    const { getBlock } = useWorldStore.getState()
    const maxSteps = Math.ceil(maxDist * 3)

    for (let i = 0; i < maxSteps; i++) {
      const block = getBlock(x, y, z)
      if (block !== BLOCK_AIR) {
        const def = BLOCK_DEFS[block]
        if (def && def.solid) {
          const result: RaycastResult = {
            blockPos: [x, y, z],
            placePos: [prevX, prevY, prevZ],
            blockId: block,
          }
          resultRef.current = result
          raycastResultRef.current = result
          setHighlightPos([x + 0.5, y + 0.5, z + 0.5])
          return
        }
      }

      prevX = x; prevY = y; prevZ = z

      // Step to next voxel boundary (DDA)
      if (tMaxX < tMaxY) {
        if (tMaxX < tMaxZ) {
          if (tMaxX > maxDist) break
          x += stepX
          tMaxX += tDeltaX
        } else {
          if (tMaxZ > maxDist) break
          z += stepZ
          tMaxZ += tDeltaZ
        }
      } else {
        if (tMaxY < tMaxZ) {
          if (tMaxY > maxDist) break
          y += stepY
          tMaxY += tDeltaY
        } else {
          if (tMaxZ > maxDist) break
          z += stepZ
          tMaxZ += tDeltaZ
        }
      }
    }

    // Nothing hit
    resultRef.current = null
    raycastResultRef.current = null
    setHighlightPos(null)
  })

  return <BlockHighlight position={highlightPos} />
}

// ─────────────────────────────────────────────────────────────
// 5. Sky – day/night cycle with proper colors and lighting
// ─────────────────────────────────────────────────────────────
const DAY_COLOR = new THREE.Color('#87CEEB')
const SUNSET_COLOR = new THREE.Color('#FF6B35')
const NIGHT_COLOR = new THREE.Color('#0A0A2E')

function Sky() {
  const dirLightRef = useRef<THREE.DirectionalLight>(null)
  const sceneRef = useRef<THREE.Scene | null>(null)
  const tempColor = useRef(new THREE.Color())

  const { scene } = useThree()

  useEffect(() => {
    sceneRef.current = scene
  }, [scene])

  useFrame(() => {
    const world = useWorldStore.getState().world
    if (!world || !dirLightRef.current) return

    const timeOfDay = world.time / DAY_LENGTH_TICKS // 0-1
    const sunAngle = timeOfDay * Math.PI * 2

    // Sun position – rotates around the horizon
    const sunX = Math.cos(sunAngle) * 100
    const sunY = Math.sin(sunAngle) * 100
    dirLightRef.current.position.set(sunX, sunY, 50)

    // Sun height determines lighting intensity and sky color
    const sunHeight = Math.sin(sunAngle) // -1 to 1

    // Light intensity
    const intensity = Math.max(0.05, sunHeight * 0.8 + 0.2)
    dirLightRef.current.intensity = intensity

    // Sky color interpolation
    const c = tempColor.current
    if (sunHeight > 0.2) {
      // Day
      c.copy(DAY_COLOR)
    } else if (sunHeight > -0.1) {
      // Sunset / sunrise transition
      const t = (sunHeight + 0.1) / 0.3
      c.copy(SUNSET_COLOR).lerp(DAY_COLOR, t)
    } else {
      // Night with possible sunset tint near horizon
      const t = Math.max(0, (sunHeight + 0.3) / 0.2)
      c.copy(NIGHT_COLOR).lerp(SUNSET_COLOR, t)
    }

    // Apply sky color and fog
    if (sceneRef.current) {
      sceneRef.current.background = c
      const fog = sceneRef.current.fog as THREE.Fog | undefined
      if (fog) fog.color.copy(c)
    }
  })

  // Fog distance based on render distance
  const rd = useSettingsStore.getState().renderDistance
  const fogNear = rd * CHUNK_WIDTH * 0.5
  const fogFar = rd * CHUNK_WIDTH * 1.1

  return (
    <>
      <color attach="background" args={['#87CEEB']} />
      <fog attach="fog" args={['#87CEEB', fogNear, fogFar]} />
      <ambientLight intensity={0.3} />
      <directionalLight
        ref={dirLightRef}
        position={[100, 200, 100]}
        intensity={0.8}
        color="#FFF5E0"
      />
      <hemisphereLight args={['#87CEEB', '#4A7023', 0.25]} />
    </>
  )
}

// ─────────────────────────────────────────────────────────────
// 6. Weather – rain and thunder particles
// ─────────────────────────────────────────────────────────────
const RAIN_COUNT = 3000
const THUNDER_COUNT = 200
const RAIN_SPEED = 1.5
const RAIN_AREA = 60

function WeatherParticles() {
  const rainRef = useRef<THREE.Points>(null)
  const thunderRef = useRef<THREE.Points>(null)
  const rainPositions = useRef<Float32Array | null>(null)
  const thunderPositions = useRef<Float32Array | null>(null)
  const thunderOpacities = useRef<Float32Array | null>(null)
  const playerRef = useRef(usePlayerStore.getState().player)
  const thunderFlash = useRef(0)

  useEffect(() => {
    const unsub = usePlayerStore.subscribe((s) => {
      playerRef.current = s.player
    })
    return unsub
  }, [])

  // Initialize rain geometry
  const rainGeometry = useMemo(() => {
    const positions = new Float32Array(RAIN_COUNT * 3)
    for (let i = 0; i < RAIN_COUNT; i++) {
      positions[i * 3] = (Math.random() - 0.5) * RAIN_AREA
      positions[i * 3 + 1] = Math.random() * 40
      positions[i * 3 + 2] = (Math.random() - 0.5) * RAIN_AREA
    }
    rainPositions.current = positions
    const geo = new THREE.BufferGeometry()
    geo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
    return geo
  }, [])

  // Initialize thunder geometry
  const thunderGeometry = useMemo(() => {
    const positions = new Float32Array(THUNDER_COUNT * 3)
    const opacities = new Float32Array(THUNDER_COUNT)
    for (let i = 0; i < THUNDER_COUNT; i++) {
      positions[i * 3] = (Math.random() - 0.5) * RAIN_AREA
      positions[i * 3 + 1] = Math.random() * 50
      positions[i * 3 + 2] = (Math.random() - 0.5) * RAIN_AREA
      opacities[i] = Math.random()
    }
    thunderPositions.current = positions
    thunderOpacities.current = opacities
    const geo = new THREE.BufferGeometry()
    geo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
    return geo
  }, [])

  // Rain material – elongated vertical look via sizeAttenuation
  const rainMaterial = useMemo(() => {
    return new THREE.PointsMaterial({
      color: '#AACCEE',
      size: 0.4,
      transparent: true,
      opacity: 0.5,
      depthWrite: false,
      sizeAttenuation: true,
    })
  }, [])

  // Thunder material
  const thunderMaterial = useMemo(() => {
    return new THREE.PointsMaterial({
      color: '#FFFFFF',
      size: 2.0,
      transparent: true,
      opacity: 0.0,
      depthWrite: false,
      sizeAttenuation: true,
    })
  }, [])

  useFrame((_, delta) => {
    const world = useWorldStore.getState().world
    const isRaining = world?.weather?.raining ?? false
    const isThundering = world?.weather?.thundering ?? false

    // Update rain visibility
    if (rainRef.current) {
      rainRef.current.visible = isRaining
    }
    if (thunderRef.current) {
      thunderRef.current.visible = isThundering
    }

    // Animate rain
    if (isRaining && rainRef.current && rainPositions.current) {
      const player = playerRef.current
      const px = player ? player.position[0] : 0
      const py = player ? player.position[1] : 80
      const pz = player ? player.position[2] : 0

      const pos = rainPositions.current
      for (let i = 0; i < RAIN_COUNT; i++) {
        // Fall
        pos[i * 3 + 1] -= RAIN_SPEED * delta * 60

        // Reset when below player
        if (pos[i * 3 + 1] + py < py - 5) {
          pos[i * 3] = (Math.random() - 0.5) * RAIN_AREA
          pos[i * 3 + 1] = 40
          pos[i * 3 + 2] = (Math.random() - 0.5) * RAIN_AREA
        }
      }

      // Center rain around player
      rainRef.current.position.set(px, py, pz)
      rainRef.current.geometry.attributes.position.needsUpdate = true
    }

    // Animate thunder
    if (isThundering && thunderRef.current) {
      const player = playerRef.current
      const px = player ? player.position[0] : 0
      const py = player ? player.position[1] : 80
      const pz = player ? player.position[2] : 0

      // Flash effect
      thunderFlash.current -= delta * 3
      if (Math.random() < 0.005) {
        thunderFlash.current = 0.5 + Math.random() * 0.5
      }

      thunderMaterial.opacity = Math.max(0, thunderFlash.current) * 0.8
      thunderRef.current.position.set(px, py + 20, pz)
    }
  })

  return (
    <>
      <points ref={rainRef} geometry={rainGeometry} material={rainMaterial} visible={false} />
      <points ref={thunderRef} geometry={thunderGeometry} material={thunderMaterial} visible={false} />
    </>
  )
}

// ─────────────────────────────────────────────────────────────
// Main Scene Export
// ─────────────────────────────────────────────────────────────
export function GameScene() {
  const { texture, loaded } = useTextureAtlas()

  if (!loaded) {
    return (
      <>
        <color attach="background" args={['#333333']} />
        <ambientLight intensity={0.5} />
        <mesh position={[0, 0, -2]}>
          <planeGeometry args={[2, 0.5]} />
          <meshBasicMaterial color="#222222" />
        </mesh>
      </>
    )
  }

  return (
    <>
      <FirstPersonCamera />
      <Sky />
      <WorldRenderer atlasTexture={texture} />
      <BlockRaycaster />
      <WeatherParticles />
    </>
  )
}
