<template>
  <div class="swarm" :style="{ '--cols': cols }">
    <span
      v-for="i in count"
      :key="i"
      class="dot"
      :class="{ lit: litSet.has(i - 1), flash: flashIdx === i - 1 }"
      :style="dotStyle(i)"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onBeforeUnmount } from 'vue'
import { useIsSlideActive } from '@slidev/client'

const props = withDefaults(defineProps<{
  count?: number
  cols?: number
  // Asymptotic mode (mirrors BotCounter): lit fraction approaches ceiling.
  litStart?: number      // 0-1, fraction lit at t=0
  litCeiling?: number    // 0-1, asymptotic ceiling
  tau?: number           // time constant in seconds — same units as BotCounter
  // Direct override: explicit list of lit indices wins over asymptotic mode.
  litIndices?: number[]
  // Spotlight: how often (ms) to briefly burst a random lit dot.
  burstEveryMs?: number
}>(), {
  count: 240,
  cols: 24,
  litStart: 0.4942,
  litCeiling: 0.5,
  tau: 600,
  litIndices: () => [],
  burstEveryMs: 1200,
})

const elapsed = ref(0)
let raf: number | null = null
let startTime: number | null = null

function tick(now: number) {
  if (startTime === null) startTime = now
  elapsed.value = (now - startTime) / 1000
  raf = requestAnimationFrame(tick)
}

// Stable shuffle so the same dots light up in the same order every render.
function shuffle(n: number, seed: number) {
  const arr = Array.from({ length: n }, (_, i) => i)
  let s = seed >>> 0
  const rng = () => {
    s = (s + 0x6D2B79F5) >>> 0
    let t = s
    t = Math.imul(t ^ (t >>> 15), 1 | t)
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296
  }
  for (let i = n - 1; i > 0; i--) {
    const j = Math.floor(rng() * (i + 1))
    ;[arr[i], arr[j]] = [arr[j], arr[i]]
  }
  return arr
}

const order = computed(() => shuffle(props.count, 1337))

const fraction = computed(() => {
  const gap = props.litCeiling - props.litStart
  return props.litCeiling - gap * Math.exp(-elapsed.value / props.tau)
})

const litSet = computed(() => {
  if (props.litIndices.length) return new Set(props.litIndices)
  const n = Math.round(fraction.value * props.count)
  return new Set(order.value.slice(0, n))
})

// Stable per-dot phase for the breathing animation. Same dot, same phase,
// so the room never sees a "reset" if Vue rerenders.
function phaseFor(i: number) {
  // 0..1, hashed off the index
  return (((i * 2654435761) >>> 0) % 1000) / 1000
}

function dotStyle(i: number) {
  const idx = i - 1
  const jitter = ((i * 37) % 7) - 3
  // CSS variables: phase for breathing delay (negative => start mid-cycle).
  return {
    transform: `translateY(${jitter}px)`,
    '--phase': `-${(phaseFor(idx) * 4).toFixed(2)}s`,
  } as Record<string, string>
}

// Spotlight: pick a random lit dot every burstEveryMs, briefly flash it.
const flashIdx = ref<number | null>(null)
let burstTimer: ReturnType<typeof setInterval> | null = null
let clearTimer: ReturnType<typeof setTimeout> | null = null

function burst() {
  const lit = Array.from(litSet.value)
  if (lit.length === 0) return
  const pick = lit[Math.floor(Math.random() * lit.length)]
  flashIdx.value = pick
  if (clearTimer) clearTimeout(clearTimer)
  clearTimer = setTimeout(() => { flashIdx.value = null }, 520)
}

function teardown() {
  if (raf !== null) { cancelAnimationFrame(raf); raf = null }
  if (burstTimer) { clearInterval(burstTimer); burstTimer = null }
  if (clearTimer) { clearTimeout(clearTimer); clearTimer = null }
  startTime = null
  elapsed.value = 0
  flashIdx.value = null
}

function kickoff() {
  startTime = null
  elapsed.value = 0
  raf = requestAnimationFrame(tick)
  burstTimer = setInterval(burst, props.burstEveryMs)
}

const isActive = useIsSlideActive()
watch(isActive, (active) => {
  teardown()
  if (active) kickoff()
}, { immediate: true })

onBeforeUnmount(teardown)
</script>

<style scoped>
.swarm {
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  gap: 8px;
  padding: 1rem 0;
  max-width: 720px;
  margin: 0 auto;
}
.dot {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(160, 160, 170, 0.28);
  transition: background 0.6s ease, box-shadow 0.6s ease;
}
.dot.lit {
  background: rgba(255, 255, 255, 0.88);
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.4);
  animation: breathe 3.6s ease-in-out infinite;
  animation-delay: var(--phase);
}
.dot.flash {
  background: #ffffff;
  box-shadow: 0 0 14px rgba(255, 255, 255, 0.95), 0 0 4px #ffffff;
  transform: scale(1.5);
  animation: none;
  transition: background 0.08s ease, box-shadow 0.08s ease, transform 0.08s ease;
}
@keyframes breathe {
  0%, 100% { opacity: 0.55; box-shadow: 0 0 4px rgba(255, 255, 255, 0.25); }
  50%      { opacity: 1.00; box-shadow: 0 0 9px rgba(255, 255, 255, 0.6); }
}
</style>
