import { useEffect, useMemo, useState } from "react";
import * as THREE from "three"
import { useFrame, useThree } from "@react-three/fiber";
import { getChunkCoordinates3D } from "./chunkutils"
import useStore from "@/lib/store";
import maps from "./chunkmap.json"
import { supabase } from "@/lib/supabaseClient";
import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/Addons.js";

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 names = useStore((s)=>s.names)
  const StarCount = useStore((s)=>s.count)
  const chunkMap :any= maps
  const colors = [0xf9c54b, 0xa891fb, 0xff9933, 0x00ccff, 0xccb0e9]
  const [loaded] = useState({data:new Map()})
  const [labelRenderer] = useState(new CSS2DRenderer())

  const createStar = (pos:THREE.Vector3, data:any, constellationIndex?: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.constellation = constellationIndex
    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) =>{
    const group = new THREE.Group()
    const starGroup = new THREE.Group()
    const points = []
    for (let i = 0; i<5 ; i++){
      while(1){
        const pos = new THREE.Vector3(Math.random() *10 -5, Math.random() *10 -5, Math.random() *10 -5)
        let j
        for (j = 0; j<points.length; j++){
          if(pos.distanceTo(points[j])<4)
            break
        }
        if(j === points.length){
          const star = createStar(pos, data[i], constellationIndex)
          starGroup.add(star)
          points.push(pos)
          break
        }
      }
    }  
    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)
    starGroup.position.set(Math.random() *_size - 0.5*_size, Math.random() *_size - 0.5*_size, Math.random() *_size - 0.5*_size)
    
    // const text = document.createElement( 'div' );
    // text.className = 'scene-label';    
    // text.textContent = names[constellationIndex % names.length];
    const label = document.createElement('div');
    label.className = "scene-label"
    label.textContent = names[constellationIndex % names.length];

    const labelObject = new CSS2DObject(label);
    starGroup.add( labelObject );
    group.add(starGroup)
    return group
  }

  const createChunk = async (chunkX:number, chunkY:number, chunkZ:number, chunkSize:number, scale:number) =>{
    const chunkKey = `${chunkX},${chunkY},${chunkZ}`; // Unique key for the chunk
    if (loaded.data.has(chunkKey)) return;
    // const _oldElement = scene.getObjectByName(chunkKey)
    // if(_oldElement){
      //   return null
      // }
      loaded.data.set(chunkKey, true);
      const newChunk = new THREE.Group(); // Each chunk is a Group (or Object3D)      
      newChunk.name = chunkKey
      newChunk.userData = {index:chunkMap[chunkKey]}
      // Add geometry or objects to the chunk
      const _size = scale/chunkSize
      const high = StarCount-(chunkMap[chunkKey]-1)*8*5+1
      const low = high-8*5-1
      // const data :any = []
      const { data, error } = await supabase.from('transaction-data-updated').select('*').gt('transaction_id', low).lt('transaction_id', high).order('transaction_id', { ascending: false });
      if (error) {
        console.error('Error fetching data:', error);
        return null
      }
      let _index = 0
      for (let x = 0; x < chunkSize; x++) {
        for (let y = 0; y < chunkSize; y++) {
          for (let z = 0; z < chunkSize; z++) {
            const constellationIndex = (chunkMap[chunkKey]-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)
            constellation.position.set((x-0.5)*_size, (y-0.5)*_size, (z-0.5)*_size);
            constellation.scale.setScalar(0.8)
            newChunk.add(constellation);            
            _index ++
          }
        }
      }
      newChunk.position.set(chunkX*scale, chunkY*scale, chunkZ*scale)
      return newChunk
  }

  const updateChunks = async (chunkX: number, chunkY: number, chunkZ: number, chunkSize: number, scale: number) =>{
    // Load chunks in a grid around the camera
    const network = scene.getObjectByName("network")
    if(network){
      const radius = 1; // Number of chunks to load around the camera
      for (let x = -radius; x <= radius; x++) {
        for (let y = -radius; y <= radius; y++) {
          for (let z = -radius; z <= radius; z++) {
            const chunk = await createChunk(chunkX + x, chunkY + y, chunkZ + z, chunkSize, scale);
            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.traverse((child:any)=>{
            if(child?.isCSS2DObject){
              if (child.element && child.element.parentNode) {
                child.element.parentNode.removeChild(child.element);
              }
            }

          })
          network.remove(_obj)
        }
      })
    }
  }

  useEffect(()=>{
    if(names?.length>0){
      const chunkSize = 2, scale = 100
      const { chunkX, chunkY, chunkZ } = getChunkCoordinates3D(focus.x, focus.y, focus.z, chunkSize, scale/2);
      if(chunk.chunkX !== chunkX || chunk.chunkY !== chunkY || chunk.chunkZ !== chunkZ){      
        setChunk({ chunkX, chunkY, chunkZ })
        updateChunks(chunkX, chunkY, chunkZ, chunkSize, scale)
      }
    }
  },[focus, names])

  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 );
    }
  },[])

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

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