type SmokeConfig = {
  color?: [number, number, number]
  minVx?: number
  maxVx?: number
  minVy?: number
  maxVy?: number
  minScale?: number
  maxScale?: number
  minLifetime?: number
  maxLifetime?: number
  particlesPerSecond?: number
  spriteSize?: number
}

type Particle = {
  x: number
  y: number
  vx: number
  vy: number
  startvy: number
  scale: number
  finalScale: number
  lifetime: number
  age: number
}

const defaultConfig: SmokeConfig = {
  color: [24, 46.8, 48.2],
  minVx: -0.04,
  maxVx: 0.04,
  minVy: -0.4,
  maxVy: -0.1,
  minScale: 0,
  maxScale: 0.5,
  minLifetime: 3000,
  maxLifetime: 8000,
  particlesPerSecond: 30,
  spriteSize: 16,
}

const floatInRange = (start: number, end: number): number =>
  start + Math.random() * (end - start)

const createParticle = (
  x: number,
  y: number,
  config: SmokeConfig,
): Particle => {
  const scale = floatInRange(config.minScale!, config.maxScale!)
  return {
    x,
    y,
    vx: floatInRange(config.minVx!, config.maxVx!),
    startvy: floatInRange(config.minVy!, config.maxVy!),
    scale,
    finalScale: floatInRange(25 + scale, 30 + scale),
    lifetime: floatInRange(config.minLifetime!, config.maxLifetime!),
    age: 0,
    vy: 0,
  }
}

const updateParticle = (particle: Particle, deltaTime: number): void => {
  particle.x += particle.vx * deltaTime
  particle.y += particle.vy * deltaTime
  const frac = Math.sqrt(particle.age / particle.lifetime)
  particle.vy = (1 - frac) * particle.startvy
  particle.age += deltaTime
  particle.scale = frac * particle.finalScale
}

const createSmokeSprite = (
  color: [number, number, number],
  size: number,
): HTMLCanvasElement => {
  const canvas = document.createElement("canvas")
  const ctx = canvas.getContext("2d")!
  const imageData = ctx.createImageData(size, size)
  const data = imageData.data

  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      const idx = (y * size + x) * 4
      const distFromCenter = Math.sqrt(
        Math.pow(x - size / 2, 2) + Math.pow(y - size / 2, 2),
      )
      const opacity = Math.max(0, 255 * (1 - distFromCenter / (size / 2)))

      data[idx] = color[0]
      data[idx + 1] = color[1]
      data[idx + 2] = color[2]
      data[idx + 3] = opacity * Math.random() * 0.3
    }
  }

  canvas.width = size
  canvas.height = size
  ctx.putImageData(imageData, 0, 0)

  // Apply Gaussian blur for smoother appearance
  ctx.filter = "blur(4px)"
  ctx.drawImage(canvas, 0, 0)
  ctx.filter = "none"

  return canvas
}

export default (element: HTMLElement, config: SmokeConfig = {}) => {
  const mergedConfig = { ...defaultConfig, ...config }
  const canvas = document.createElement("canvas")
  const particles: Particle[] = []
  let animationFrame: number
  let lastFrame = 0
  let isRunning = false
  let isDestroying = false

  const setupCanvas = () => {
    canvas.style.position = "fixed"
    canvas.style.top = "0"
    canvas.style.left = "0"
    canvas.style.pointerEvents = "none"
    canvas.style.zIndex = "9999"
    canvas.width = window.innerWidth * window.devicePixelRatio
    canvas.height = window.innerHeight * window.devicePixelRatio
    canvas.style.width = `${window.innerWidth}px`
    canvas.style.height = `${window.innerHeight}px`
    document.body.appendChild(canvas)
  }

  const drawParticle = (
    ctx: CanvasRenderingContext2D,
    particle: Particle,
    sprite: HTMLCanvasElement,
  ) => {
    ctx.globalAlpha =
      (1 - Math.abs(1 - (2 * particle.age) / particle.lifetime)) / 8
    const off = (particle.scale * mergedConfig.spriteSize!) / 2
    ctx.drawImage(sprite, particle.x - off, particle.y - off, off * 2, off * 2)
  }

  const updateAndDraw = (timestamp: number) => {
    if (!isRunning) return

    const deltaTime = timestamp - lastFrame
    lastFrame = timestamp

    const ctx = canvas.getContext("2d")!
    const sprite = createSmokeSprite(
      mergedConfig.color!,
      mergedConfig.spriteSize!,
    )

    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.scale(window.devicePixelRatio, window.devicePixelRatio)

    particles.forEach((p) => updateParticle(p, deltaTime))
    particles.splice(
      0,
      particles.length,
      ...particles.filter((p) => p.age < p.lifetime),
    )

    particles.forEach((p) => drawParticle(ctx, p, sprite))

    if (!isDestroying) {
      const rect = element.getBoundingClientRect()
      for (let i = 0; i < mergedConfig.particlesPerSecond! / 60; i++) {
        particles.push(
          createParticle(
            rect.left + Math.random() * rect.width,
            rect.bottom,
            mergedConfig,
          ),
        )
      }
    }

    ctx.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio)

    if (particles.length > 0) {
      animationFrame = requestAnimationFrame(updateAndDraw)
    } else {
      isRunning = false
      canvas.remove()
    }
  }

  setupCanvas()

  const resizeHandler = () => {
    canvas.width = window.innerWidth * window.devicePixelRatio
    canvas.height = window.innerHeight * window.devicePixelRatio
    canvas.style.width = `${window.innerWidth}px`
    canvas.style.height = `${window.innerHeight}px`
  }
  window.addEventListener("resize", resizeHandler)

  return {
    start: () => {
      if (!isRunning) {
        isRunning = true
        isDestroying = false
        lastFrame = performance.now()
        animationFrame = requestAnimationFrame(updateAndDraw)
      }
    },

    stop: () => {
      isRunning = false
      cancelAnimationFrame(animationFrame)
    },

    destroy: () => {
      isDestroying = true
      if (!isRunning) {
        canvas.remove()
        window.removeEventListener("resize", resizeHandler)
      }
      // Animation will stop naturally when particles are gone
    },
  }
}
