'use client'
import type { ReactNode } from 'react'
import React, { useEffect, useRef } from 'react'

export interface BaseParticle {
   element: HTMLElement | SVGSVGElement
   left: number
   size: number
   top: number
}

export interface BaseParticleOptions {
   particle?: string
   size?: number
}

export interface CoolParticle extends BaseParticle {
   direction: number
   speedHorz: number
   speedUp: number
   spinSpeed: number
   spinVal: number
}

export interface CoolParticleOptions extends BaseParticleOptions {
   particleCount?: number
   speedHorz?: number
   speedUp?: number
}

function getContainer() {
   const id = '_coolMode_effect'
   const existingContainer = document.getElementById(id)

   if (existingContainer)
      return existingContainer

   const container = document.createElement('div')
   container.setAttribute('id', id)
   container.setAttribute(
      'style',
      'overflow:hidden; position:fixed; height:100%; top:0; left:0; right:0; bottom:0; pointer-events:none; z-index:2147483647',
   )

   document.body.appendChild(container)

   return container
}

let instanceCounter = 0

function applyParticleEffect(
   element: HTMLElement,
   options?: CoolParticleOptions,
): () => void {
   instanceCounter++

   const defaultParticle = 'circle'
   const particleType = options?.particle || defaultParticle
   const sizes = [15, 20, 25, 35, 45]
   const limit = 45

   let particles: CoolParticle[] = []
   let autoAddParticle = false
   let mouseX = 0
   let mouseY = 0

   const container = getContainer()

   function generateParticle() {
      const size
      = options?.size || sizes[Math.floor(Math.random() * sizes.length)]
      const speedHorz = options?.speedHorz || Math.random() * 10
      const speedUp = options?.speedUp || Math.random() * 25
      const spinVal = Math.random() * 360
      const spinSpeed = Math.random() * 35 * (Math.random() <= 0.5 ? -1 : 1)
      const top = mouseY - size / 2
      const left = mouseX - size / 2
      const direction = Math.random() <= 0.5 ? -1 : 1

      const particle = document.createElement('div')

      if (particleType === 'circle') {
         const svgNS = 'http://www.w3.org/2000/svg'
         const circleSVG = document.createElementNS(svgNS, 'svg')
         const circle = document.createElementNS(svgNS, 'circle')
         circle.setAttributeNS(null, 'cx', (size / 2).toString())
         circle.setAttributeNS(null, 'cy', (size / 2).toString())
         circle.setAttributeNS(null, 'r', (size / 2).toString())
         circle.setAttributeNS(
            null,
            'fill',
        `hsl(${Math.random() * 360}, 70%, 50%)`,
         )

         circleSVG.appendChild(circle)
         circleSVG.setAttribute('width', size.toString())
         circleSVG.setAttribute('height', size.toString())

         particle.appendChild(circleSVG)
      }
      else {
         particle.innerHTML = `<img src="${particleType}" width="${size}" height="${size}" style="border-radius: 50%">`
      }

      particle.style.position = 'absolute'
      particle.style.transform = `translate3d(${left}px, ${top}px, 0px) rotate(${spinVal}deg)`

      container.appendChild(particle)

      particles.push({
         direction,
         element: particle,
         left,
         size,
         speedHorz,
         speedUp,
         spinSpeed,
         spinVal,
         top,
      })
   }

   function refreshParticles() {
      particles.forEach((p) => {
         p.left = p.left - p.speedHorz * p.direction
         p.top = p.top - p.speedUp
         p.speedUp = Math.min(p.size, p.speedUp - 1)
         p.spinVal = p.spinVal + p.spinSpeed

         if (
            p.top
            >= Math.max(window.innerHeight, document.body.clientHeight) + p.size
         ) {
            particles = particles.filter(o => o !== p)
            p.element.remove()
         }

         p.element.setAttribute(
            'style',
            [
               'position:absolute',
               'will-change:transform',
          `top:${p.top}px`,
          `left:${p.left}px`,
          `transform:rotate(${p.spinVal}deg)`,
            ].join(';'),
         )
      })
   }

   let animationFrame: number | undefined

   let lastParticleTimestamp = 0
   const particleGenerationDelay = 30

   function loop() {
      const currentTime = performance.now()
      if (
         autoAddParticle
         && particles.length < limit
         && currentTime - lastParticleTimestamp > particleGenerationDelay
      ) {
         generateParticle()
         lastParticleTimestamp = currentTime
      }

      refreshParticles()
      animationFrame = requestAnimationFrame(loop)
   }

   loop()

   const isTouchInteraction = 'ontouchstart' in window

   const tap = isTouchInteraction ? 'touchstart' : 'mousedown'
   const tapEnd = isTouchInteraction ? 'touchend' : 'mouseup'
   const move = isTouchInteraction ? 'touchmove' : 'mousemove'

   const updateMousePosition = (e: MouseEvent | TouchEvent) => {
      if ('touches' in e) {
         mouseX = e.touches?.[0].clientX
         mouseY = e.touches?.[0].clientY
      }
      else {
         mouseX = e.clientX
         mouseY = e.clientY
      }
   }

   const tapHandler = (e: MouseEvent | TouchEvent) => {
      updateMousePosition(e)
      autoAddParticle = true
   }

   const disableAutoAddParticle = () => {
      autoAddParticle = false
   }

   element.addEventListener(move, updateMousePosition, { passive: true })
   element.addEventListener(tap, tapHandler, { passive: true })
   element.addEventListener(tapEnd, disableAutoAddParticle, { passive: true })
   element.addEventListener('mouseleave', disableAutoAddParticle, {
      passive: true,
   })

   return () => {
      element.removeEventListener(move, updateMousePosition)
      element.removeEventListener(tap, tapHandler)
      element.removeEventListener(tapEnd, disableAutoAddParticle)
      element.removeEventListener('mouseleave', disableAutoAddParticle)

      const interval = setInterval(() => {
         if (animationFrame && particles.length === 0) {
            cancelAnimationFrame(animationFrame)
            clearInterval(interval)

            if (--instanceCounter === 0)
               container.remove()
         }
      }, 500)
   }
}

interface CoolModeProps {
   children: ReactNode
   options?: CoolParticleOptions
}

export const CoolMode: React.FC<CoolModeProps> = ({ children, options }) => {
   const ref = useRef<HTMLElement>(null)

   useEffect(() => {
      if (ref.current)
         return applyParticleEffect(ref.current, options)
   }, [options])

   return React.cloneElement(children as React.ReactElement, { ref })
}
