import {useCallback, useRef, useState} from 'react'

import {emptySlot, type Slot} from '@/lib/async-slot'
import {cleanError} from '@/lib/errors'
import {staleGuard} from '@/lib/stale-guard'

// the component-local resource: unlike the store loaders it resets data to
// initial on each run, so a new query never shows the previous result. the
// guard keeps an older in-flight run (or one racing a reset) from committing
export const useAsyncResource = <T, A extends unknown[]>(
  fetcher: (...args: A) => Promise<T>,
  initialData: T,
) => {
  const [state, setState] = useState<Slot<T>>(() =>
    emptySlot(initialData),
  )
  const guardRef = useRef(staleGuard())

  const run = useCallback(
    async (...args: A) => {
      const token = guardRef.current.claim()
      setState({loading: true, error: null, data: initialData})
      try {
        const data = await fetcher(...args)
        if (!guardRef.current.isCurrent(token)) return
        setState({loading: false, error: null, data})
      } catch (err) {
        if (!guardRef.current.isCurrent(token)) return
        setState({
          loading: false,
          error: cleanError(err),
          data: initialData,
        })
      }
    },
    [fetcher, initialData],
  )

  const reset = useCallback(() => {
    guardRef.current.claim()
    setState({loading: false, error: null, data: initialData})
  }, [initialData])

  return {...state, run, reset}
}
