<script lang="ts">
  import { onMount } from 'svelte'
  import { getTheme } from '$lib/stores/theme.svelte'

  interface Particle {
    x: number
    y: number
    vx: number
    vy: number
    r: number
    phase: number
    pulse: number
  }

  const COUNT = 90
  const CONNECT_DIST = 160
  const MOUSE_RADIUS = 200
  const MOUSE_FORCE = 0.012

  let host = $state<HTMLDivElement | null>(null)
  let canvas = $state<HTMLCanvasElement | null>(null)
  let particles: Particle[] = []
  let mouse = { x: -500, y: -500 }
  let w = 0, h = 0, dpr = 1
  let timer: ReturnType<typeof setTimeout> | 0 = 0

  function spawn() {
    particles = Array.from({ length: COUNT }, () => ({
      x: Math.random() * w,
      y: Math.random() * h,
      vx: (Math.random() - 0.5) * 0.3,
      vy: (Math.random() - 0.5) * 0.3,
      r: 1 + Math.random() * 2,
      phase: Math.random() * Math.PI * 2,
      pulse: 0.4 + Math.random() * 0.6
    }))
  }

  function resize() {
    if (!host || !canvas) return
    dpr = Math.min(window.devicePixelRatio || 1, 2)
    w = host.clientWidth
    h = host.clientHeight
    canvas.width = Math.round(w * dpr)
    canvas.height = Math.round(h * dpr)
    if (particles.length === 0) spawn()
  }

  function onPointerMove(e: PointerEvent) {
    if (!host) return
    const rect = host.getBoundingClientRect()
    mouse.x = e.clientX - rect.left
    mouse.y = e.clientY - rect.top
  }

  function onPointerLeave() {
    mouse.x = -500
    mouse.y = -500
  }

  function draw(ts: number) {
    timer = setTimeout(() => draw(performance.now()), 33)
    if (!canvas || particles.length === 0) return
    const ctx = canvas.getContext('2d')
    if (!ctx) return

    const t = ts / 1000
    const dark = getTheme() !== 'light'

    ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
    ctx.clearRect(0, 0, w, h)

    // update + draw particles
    for (const p of particles) {
      // autonomous drift
      p.vx += (Math.sin(t * 0.4 + p.phase) * 0.003) * 0.15
      p.vy += (Math.cos(t * 0.35 + p.phase) * 0.003) * 0.15

      // mouse interaction
      const dx = p.x - mouse.x
      const dy = p.y - mouse.y
      const dist = Math.sqrt(dx * dx + dy * dy)
      if (dist < MOUSE_RADIUS && dist > 0) {
        const force = (MOUSE_RADIUS - dist) / MOUSE_RADIUS * MOUSE_FORCE
        p.vx += (dx / dist) * force * 0.6
        p.vy += (dy / dist) * force * 0.6
      }

      // friction + random nudge
      p.vx *= 0.995
      p.vy *= 0.995
      if (Math.random() < 0.003) {
        p.vx += (Math.random() - 0.5) * 0.04
        p.vy += (Math.random() - 0.5) * 0.04
      }

      // wrap
      p.x += p.vx
      p.y += p.vy
      if (p.x < -40) p.x = w + 40
      if (p.x > w + 40) p.x = -40
      if (p.y < -40) p.y = h + 40
      if (p.y > h + 40) p.y = -40

      // pulse brightness
      const alpha = p.pulse * (0.35 + 0.15 * Math.sin(t * 1.2 + p.phase))
      ctx.fillStyle = dark
        ? `rgba(140,175,230,${alpha})`
        : `rgba(60,90,140,${alpha})`
      ctx.beginPath()
      ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2)
      ctx.fill()
    }

    // connections between nearby particles
    ctx.lineWidth = 0.6
    for (let i = 0; i < particles.length; i++) {
      for (let j = i + 1; j < particles.length; j++) {
        const a = particles[i]
        const b = particles[j]
        const dx = a.x - b.x
        const dy = a.y - b.y
        const dist = dx * dx + dy * dy
        if (dist < CONNECT_DIST * CONNECT_DIST) {
          const alpha = (1 - Math.sqrt(dist) / CONNECT_DIST) * 0.18
          ctx.strokeStyle = dark
            ? `rgba(140,175,230,${alpha})`
            : `rgba(60,90,140,${alpha})`
          ctx.beginPath()
          ctx.moveTo(a.x, a.y)
          ctx.lineTo(b.x, b.y)
          ctx.stroke()
        }
      }
    }

    // radial scrim to keep center legible
    const cx = w / 2, cy = h / 2
    const scrim = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.hypot(cx, cy))
    const base = dark ? '13,17,23' : '255,255,255'
    scrim.addColorStop(0, `rgba(${base},0.72)`)
    scrim.addColorStop(0.35, `rgba(${base},0.40)`)
    scrim.addColorStop(0.65, `rgba(${base},0.08)`)
    scrim.addColorStop(1, 'rgba(0,0,0,0)')
    ctx.fillStyle = scrim
    ctx.fillRect(0, 0, w, h)
  }

  onMount(() => {
    resize()
    spawn()
    const ro = new ResizeObserver(() => {
      resize()
      spawn()
    })
    if (host) ro.observe(host)
    window.addEventListener('pointermove', onPointerMove)
    window.addEventListener('pointerleave', onPointerLeave)
    timer = setTimeout(() => draw(performance.now()), 33)
    return () => {
      clearTimeout(timer)
      ro.disconnect()
      window.removeEventListener('pointermove', onPointerMove)
      window.removeEventListener('pointerleave', onPointerLeave)
    }
  })
</script>

<div bind:this={host} class="absolute inset-0 overflow-hidden bg-background">
  <canvas bind:this={canvas} class="h-full w-full"></canvas>
</div>
