// Tapestry live tail. Short-poll the current UTC minute every 3 s while the
// screen is focused AND the app is foregrounded — no SSE/WebSocket polyfill,
// no heartbeat, no reconnect logic. Server caches the current minute with a
// short `s-maxage`, so the polling is cheap on the wire.

import { useFocusEffect } from 'expo-router'
import { useCallback, useEffect, useRef, useState } from 'react'
import { AppState, type AppStateStatus } from 'react-native'
import { ContextForActiveAccount } from 'src/lib/api-context'
import { currentBucket } from './window'

type BucketRow = { bucket: string; letters: string }

const POLL_INTERVAL_MS = 3_000

export type TapestryLive = {
  liveBucket: string
  liveLetters: string
}

/**
 * Poll the current minute's letters for `host`. The `host` argument re-keys
 * the effect so account-switching to a different instance restarts the poll
 * cleanly under the new bearer.
 *
 * The hook owns its tick-loop directly rather than going through
 * `useQuery({ refetchInterval })` — react-query would either share the
 * minute's cache across mounts (subtle UI flicker when two screens disagree
 * on which minute is "current") or pay a setup overhead per tick. The
 * 3 s minute-bucket cadence is short enough that direct `setTimeout` chained
 * calls are simpler to reason about than a queryClient-mediated path.
 */
export function useTapestryPoll(host: string): TapestryLive {
  const [live, setLive] = useState<TapestryLive>(() => ({
    liveBucket: currentBucket(),
    liveLetters: '',
  }))

  // Refs avoid restarting the tick loop on every state change. The loop
  // reads `focusedRef`/`fgRef` to decide whether to fetch; `lastBucketRef`
  // detects minute rollovers and discards stale in-flight responses when
  // the minute rolled mid-fetch.
  const focusedRef = useRef(false)
  const fgRef = useRef(AppState.currentState === 'active')
  const lastBucketRef = useRef<string>(currentBucket())

  useFocusEffect(
    useCallback(() => {
      focusedRef.current = true
      return () => {
        focusedRef.current = false
      }
    }, [])
  )

  useEffect(() => {
    const sub = AppState.addEventListener('change', (s: AppStateStatus) => {
      fgRef.current = s === 'active'
    })
    return () => sub.remove()
  }, [])

  useEffect(() => {
    // Reset for a fresh host. The new instance's letters are unrelated to
    // the prior one's, so showing prior letters even for a frame would be
    // misleading.
    const initialBucket = currentBucket()
    lastBucketRef.current = initialBucket
    setLive({ liveBucket: initialBucket, liveLetters: '' })

    let cancelled = false
    let timer: ReturnType<typeof setTimeout> | null = null

    const schedule = () => {
      timer = setTimeout(tick, POLL_INTERVAL_MS)
    }

    const tick = async () => {
      if (cancelled) return
      if (!focusedRef.current || !fgRef.current) {
        schedule()
        return
      }

      const minute = currentBucket()
      if (minute !== lastBucketRef.current) {
        lastBucketRef.current = minute
        setLive({ liveBucket: minute, liveLetters: '' })
      }

      try {
        const ctx = ContextForActiveAccount()
        const res = await ctx.request(`/api/v1/tapestry/bucket/${minute}`, {
          method: 'GET',
        })
        const rows = (await res.json()) as BucketRow[]
        if (cancelled) return
        // Drop the response on the floor if the minute rolled mid-fetch —
        // stale letters under the wrong bucket would briefly mis-attribute.
        if (lastBucketRef.current !== minute) return
        const letters = rows[0]?.letters ?? ''
        setLive((prev) =>
          prev.liveBucket === minute && prev.liveLetters === letters
            ? prev
            : { liveBucket: minute, liveLetters: letters }
        )
      } catch {
        // Drop the tick — next one fills the gap. The user reads "new
        // letters appeared with a 3 s delay" the same way they'd read a
        // single missed tick.
      }

      if (!cancelled) schedule()
    }

    void tick()

    return () => {
      cancelled = true
      if (timer) clearTimeout(timer)
    }
  }, [host])

  return live
}
