import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Stars } from '@react-three/drei'
import { useEffect, useMemo, useRef, useState } from 'react'
import * as THREE from 'three'

const LAND = new THREE.Color('#4f8f78')
const LAND_LIGHT = new THREE.Color('#78ad83')
const OCEAN = new THREE.Color('#163f51')
const ICE = new THREE.Color('#d7f1e8')

function terrainValue(lat: number, lon: number) {
  const waves =
    Math.sin(lon * 1.7 + Math.cos(lat * 2.3)) +
    Math.sin(lon * 3.1 - lat * 1.8) * 0.55 +
    Math.cos(lat * 4.2 + lon * 0.7) * 0.42
  const continentalBias = Math.sin(lon * 0.48 - 0.8) * Math.cos(lat * 1.4)
  return waves + continentalBias
}

function VoxelSphere({ reduceMotion }: { reduceMotion: boolean }) {
  const mesh = useRef<THREE.InstancedMesh>(null)
  const group = useRef<THREE.Group>(null)
  const dummy = useMemo(() => new THREE.Object3D(), [])
  const points = useMemo(() => {
    const values: { position: THREE.Vector3; scale: number; color: THREE.Color }[] = []
    const latitudeBands = 31
    for (let y = 0; y < latitudeBands; y += 1) {
      const v = y / (latitudeBands - 1)
      const phi = v * Math.PI
      const ringSize = Math.max(8, Math.round(Math.sin(phi) * 62))
      for (let x = 0; x < ringSize; x += 1) {
        const theta = (x / ringSize) * Math.PI * 2
        const lat = Math.PI / 2 - phi
        const terrain = terrainValue(lat, theta)
        const polar = Math.abs(lat) > 1.25
        const isLand = terrain > 0.42
        const radius = isLand ? 2.24 : 2.15
        const position = new THREE.Vector3(
          radius * Math.sin(phi) * Math.cos(theta),
          radius * Math.cos(phi),
          radius * Math.sin(phi) * Math.sin(theta),
        )
        const color = polar ? ICE : isLand ? (terrain > 1.2 ? LAND_LIGHT : LAND) : OCEAN
        values.push({ position, scale: isLand ? 0.15 : 0.115, color })
      }
    }
    return values
  }, [])

  useEffect(() => {
    if (!mesh.current) return
    points.forEach((point, index) => {
      dummy.position.copy(point.position)
      dummy.lookAt(0, 0, 0)
      dummy.rotateX(Math.PI / 2)
      const startScale = reduceMotion ? point.scale : 0
      dummy.scale.set(startScale, startScale, startScale)
      dummy.updateMatrix()
      mesh.current?.setMatrixAt(index, dummy.matrix)
      mesh.current?.setColorAt(index, point.color)
    })
    mesh.current.instanceMatrix.needsUpdate = true
    if (mesh.current.instanceColor) mesh.current.instanceColor.needsUpdate = true
  }, [dummy, points, reduceMotion])

  useFrame((state, delta) => {
    if (!mesh.current || !group.current) return
    if (!reduceMotion) group.current.rotation.y += delta * 0.055
    const progress = reduceMotion ? 1 : Math.min(1, state.clock.elapsedTime / 1.8)
    const eased = 1 - Math.pow(1 - progress, 3)
    points.forEach((point, index) => {
      const s = point.scale * eased
      dummy.position.copy(point.position).multiplyScalar(0.8 + eased * 0.2)
      dummy.lookAt(0, 0, 0)
      dummy.rotateX(Math.PI / 2)
      dummy.scale.set(s, s, s)
      dummy.updateMatrix()
      mesh.current?.setMatrixAt(index, dummy.matrix)
    })
    mesh.current.instanceMatrix.needsUpdate = true
  })

  return (
    <group ref={group} rotation={[0.12, -0.55, -0.08]}>
      <instancedMesh ref={mesh} args={[undefined, undefined, points.length]}>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial roughness={0.72} metalness={0.05} />
      </instancedMesh>
      <mesh>
        <sphereGeometry args={[2.02, 48, 48]} />
        <meshBasicMaterial color="#082938" transparent opacity={0.65} depthWrite={false} />
      </mesh>
      <Beacon position={[1.35, 1.58, 1.15]} delay={0.6} />
      <Beacon position={[-1.72, 0.75, 1.25]} delay={1} />
      <Beacon position={[1.7, -0.15, -1.28]} delay={1.35} />
    </group>
  )
}

function Beacon({ position, delay }: { position: [number, number, number]; delay: number }) {
  const beacon = useRef<THREE.Group>(null)
  const { surfacePosition, orientation } = useMemo(() => {
    const normal = new THREE.Vector3(...position).normalize()
    return {
      surfacePosition: normal.clone().multiplyScalar(2.3),
      orientation: new THREE.Quaternion().setFromUnitVectors(
        new THREE.Vector3(0, 1, 0),
        normal,
      ),
    }
  }, [position])

  useFrame((state) => {
    if (!beacon.current) return
    const p = Math.max(0, Math.min(1, (state.clock.elapsedTime - delay) * 2))
    beacon.current.scale.y = p
    beacon.current.visible = p > 0
  })
  return (
    <group ref={beacon} position={surfacePosition} quaternion={orientation}>
      <mesh position={[0, 0.3, 0]} renderOrder={1}>
        <boxGeometry args={[0.035, 0.6, 0.035]} />
        <meshBasicMaterial color="#e7b85b" transparent opacity={0.75} depthWrite={false} />
      </mesh>
      <mesh position={[0, 0.65, 0]} renderOrder={1}>
        <octahedronGeometry args={[0.09]} />
        <meshBasicMaterial color="#ffd680" transparent opacity={1} depthWrite={false} />
      </mesh>
    </group>
  )
}

export default function VoxelEarth() {
  const [reduceMotion, setReduceMotion] = useState(false)
  useEffect(() => {
    const query = window.matchMedia('(prefers-reduced-motion: reduce)')
    const update = () => setReduceMotion(query.matches)
    update()
    query.addEventListener('change', update)
    return () => query.removeEventListener('change', update)
  }, [])

  return (
    <div className="earth-canvas" aria-label="可拖拽旋转的方块地球模型">
      <Canvas camera={{ position: [0, 0.15, 6.9], fov: 39 }} dpr={[1, 1.65]}>
        <ambientLight intensity={1.8} />
        <directionalLight position={[4, 6, 5]} intensity={3.2} color="#e8fff5" />
        <pointLight position={[-5, -2, -4]} intensity={8} color="#287b8b" />
        <VoxelSphere reduceMotion={reduceMotion} />
        {!reduceMotion && <Stars radius={45} depth={15} count={420} factor={1.4} saturation={0} fade speed={0.15} />}
        <OrbitControls enableZoom={false} enablePan={false} rotateSpeed={0.38} minPolarAngle={0.7} maxPolarAngle={2.4} />
      </Canvas>
      <div className="earth-orbit earth-orbit-one" />
      <div className="earth-orbit earth-orbit-two" />
      <span className="earth-coordinate earth-coordinate-a">31.30° N / 120.58° E</span>
      <span className="earth-coordinate earth-coordinate-b">WORLD SCALE · 73,728 × 36,864</span>
    </div>
  )
}
