import { useEffect, useState } from "react";
import * as THREE from "three"
import { useFrame, useThree } from "@react-three/fiber";
import { getChunkCoordinates3D, offset } from "./chunkutils"
import useStore from "@/lib/store";
import { supabase } from "@/lib/supabaseClient";
import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/Addons.js";
import { UNIT, CHUNK_SIZE, RADIUS } from "@/lib/constants";
import starData from "./data.json"

export default function Chunks() {

  const { scene, camera } = useThree()
  
  const focus = useStore((s)=>s.focus)
  const [chunk, setChunk] = useState({chunkX: 0,chunkY: 0,chunkZ: 0})  
  const update = useStore((s)=>s.update)
  const colors = [0xf9c54b, 0xa891fb, 0xff9933, 0x00ccff, 0xccb0e9]
  const [loaded] = useState({data:new Map()})
  const [labelRenderer] = useState(new CSS2DRenderer())
  const chunkMap = starData.chunkMap
  const names = starData.names

  const createStar = (pos:THREE.Vector3, data:any, constellationIndex:number, chunkIndex:number) => {
    const _rr = data? Math.cbrt( data.amount * 0.239 ) / 5000 : 0.2
    const rr = (_rr>2.5)?2.5:(_rr<0.2)?0.2:_rr
    const color = colors[Math.floor(Math.random() * 4.5)]
    const material = new THREE.MeshLambertMaterial({color:color})
    const geometry = new THREE.SphereGeometry(rr, 16, 16)
    const mesh = new THREE.Mesh(geometry, material)
    if(data) {
      data.constellationIndex = constellationIndex
      data.chunkIndex = chunkIndex
    }
    mesh.name = data ? data.transaction_signature : undefined
    mesh.userData = data ? data : undefined
    mesh.position.copy(pos)
    return mesh
  }

  const createConstellation =(_size:number, data:any[], constellationIndex:number, chunkIndex:number) =>{
    // constellationIndex=== 1 && console.log(constellationIndex, data)
    const starGroup = new THREE.Group()
    const points: any[] = []
    data.map((item)=>{
      if(item){
        const pos = new THREE.Vector3(item.x, item.y, item.z)
        const star = createStar(pos, item, constellationIndex, chunkIndex)
        starGroup.add(star)
        points.push(pos)
      }
    })
    if (points.length>0)
      points.push(points[0])
    const material = new THREE.LineBasicMaterial({
      color: 0x888888
    });
    const geometry = new THREE.BufferGeometry().setFromPoints( points );
    const line = new THREE.Line( geometry, material );
    line.name = "line"
    line.userData = {constellation:constellationIndex}
    starGroup.add(line)
    // starGroup.scale.setScalar(1.5)
  
    const label = document.createElement('div');
    label.className = "scene-label"
    label.textContent = names[constellationIndex % names.length];

    const labelObject = new CSS2DObject(label);
    labelObject.name = `constellation${constellationIndex}_label`
    starGroup.add( labelObject );
    starGroup.name = `constellation${constellationIndex}`
    const mesh = new THREE.Mesh(new THREE.BoxGeometry(UNIT/CHUNK_SIZE, UNIT/CHUNK_SIZE, UNIT/CHUNK_SIZE), new THREE.MeshBasicMaterial({color:0x990000, wireframe:true}))
    // starGroup.add(mesh)
    return starGroup
  }

  const createChunk = async (chunkX:number, chunkY:number, chunkZ:number, chunkKey:string) =>{
    const newChunk = new THREE.Group(); // Each chunk is a Group (or Object3D)      
    newChunk.name = chunkKey
    const chunkIndex = chunkMap[chunkKey as keyof typeof chunkMap]
    newChunk.userData = {index:chunkIndex}
    // Add geometry or objects to the chunk
    const _size = UNIT/CHUNK_SIZE
    const low = (chunkIndex-1)*8*5
    const high = low + 40 + 1
    const { data, error } = await supabase.from('transaction-data-updated').select('*').gt('transaction_id', low).lt('transaction_id', high).order('transaction_id', { ascending: true });
    
    if (error) {
      console.error('Error fetching data:', error);
      return null
    }
    let _index = 0
    for (let x = 0; x < CHUNK_SIZE; x++) {
      for (let y = 0; y < CHUNK_SIZE; y++) {
        for (let z = 0; z < CHUNK_SIZE; z++) {
          if(data[_index*5]){
            const constellationIndex = (chunkMap[chunkKey as keyof typeof chunkMap]-1) * 8 + _index
            const newArray = [data[_index*5], data[_index*5+1], data[_index*5+2], data[_index*5+3], data[_index*5+4]]
            const constellation = createConstellation(_size, newArray, constellationIndex, chunkIndex)            
            const _offset = offset(constellationIndex)
            constellation.position.set((x-0.5)*_size + _offset.x, (y-0.5)*_size + _offset.y, (z-0.5)*_size + _offset.z);
            newChunk.add(constellation);
          }
          _index ++
        }
      }
    }
    newChunk.position.set(chunkX*UNIT, chunkY*UNIT, chunkZ*UNIT)
    
    const mesh = new THREE.Mesh(new THREE.BoxGeometry(UNIT, UNIT, UNIT), new THREE.MeshBasicMaterial({color:0x227722, transparent:true, opacity:0.5}))
    // newChunk.add(mesh)
    return newChunk
  }

  const updateChunks = async (chunkX: number, chunkY: number, chunkZ: number) =>{
    // Load chunks in a grid around the camera
    const network = scene.getObjectByName("network")
    if(network){
      for (let x = -RADIUS; x <= RADIUS; x++) {
        for (let y = -RADIUS; y <= RADIUS; y++) {
          for (let z = -RADIUS; z <= RADIUS; z++) {
            const chunkKey = `${chunkX + x},${chunkY + y},${chunkZ + z}`; // Unique key for the chunk
            if (loaded.data.has(chunkKey))
              continue;
            loaded.data.set(chunkKey, true);
            const chunk = await createChunk(chunkX + x, chunkY + y, chunkZ + z, chunkKey);
            if(chunk) network.add(chunk)
          }
        }
      }
      // Unload chunks that are too far away
      const removeGroup : any = []
      for (const chunkKey of loaded.data.keys()) {
        const [x, y, z] = chunkKey.split(",").map(Number);
        if ( Math.abs(x - chunkX) > RADIUS || Math.abs(y - chunkY) > RADIUS || Math.abs(z - chunkZ) > RADIUS ) {
          removeGroup.push(chunkKey)
          loaded.data.delete(chunkKey); // Free memory
        }
      }
      removeGroup.map((item:string)=>{
        const _obj = scene.getObjectByName(item)
        if(_obj){
          _obj?.children.map((item)=>{
            const name = item.name
            const child:any =_obj.getObjectByName(`${name}_label`)
            if(child && child.element && child.element.parentNode){
              child.element.parentNode.removeChild(child.element);
            }
          })
          network.remove(_obj)
        }
      })

      update.flag = false
    }
  }

  useEffect(()=>{
    const { chunkX, chunkY, chunkZ } = getChunkCoordinates3D(focus.x, focus.y, focus.z);
    if(chunk.chunkX !== chunkX || chunk.chunkY !== chunkY || chunk.chunkZ !== chunkZ){
      // update.flag = true
      setChunk({ chunkX, chunkY, chunkZ })
      updateChunks(chunkX, chunkY, chunkZ)
    }
  },[focus])

  useEffect(()=>{
    labelRenderer.setSize( window.innerWidth, window.innerHeight );    
    labelRenderer.domElement.style.position = 'absolute';
    labelRenderer.domElement.style.top = '0px';
    labelRenderer.domElement.style.color = 'white';
    labelRenderer.domElement.style.fontStyle = 'bold';
    labelRenderer.domElement.style.fontSize = '10px';
    labelRenderer.domElement.style.pointerEvents = 'none'
    const dom = document.getElementById( 'canvas-wrapper' )
    if(dom){
      dom.appendChild( labelRenderer.domElement );
    }
    const { chunkX, chunkY, chunkZ } = getChunkCoordinates3D(focus.x, focus.y, focus.z);
    update.flag = true
    updateChunks(chunkX, chunkY, chunkZ)
  },[])

  useFrame(()=>{
    labelRenderer.render(scene, camera);
  })

  return (
    <group name="network">
      
    </group>
  )
}
