import {create} from 'zustand'

import {cleanError} from '@/lib/errors'
import {type Snapshot} from '@/lib/snapshot-cache'
import {staleGuard, type StaleGuard} from '@/lib/stale-guard'
import {registerReset} from '@/lib/store-reset'

// one async slot: the loading → data / error machine every resource shares.
// runSlot keeps the prior data while loading so a refresh never blanks the ui;
// the fetcher owns the rpc call + parse and throws to surface an error. the
// guard stops an older overlapping run from committing over a newer one
export type Slot<T> = {
  data: T
  loading: boolean
  error: string | null
}

export const emptySlot = <T>(data: T): Slot<T> => ({
  data,
  loading: false,
  error: null,
})

export const runSlot = async <T>(
  write: (patch: Partial<Slot<T>>) => void,
  fetcher: () => Promise<T>,
  guard: Pick<StaleGuard, 'claim' | 'isCurrent'>,
): Promise<void> => {
  const token = guard.claim()
  write({loading: true, error: null})
  try {
    const data = await fetcher()
    if (!guard.isCurrent(token)) return
    write({data, loading: false, error: null})
  } catch (err) {
    if (!guard.isCurrent(token)) return
    write({loading: false, error: cleanError(err)})
  }
}

export type SlotStore<T> = Slot<T> & {refresh: () => Promise<void>}

// the selector-hook set every slot store consumer re-exports, destructured
// with renames so each store keeps its own vocabulary
export const slotHooks = <T>(
  store: <U>(selector: (s: SlotStore<T>) => U) => U,
) => ({
  useData: () => store((s) => s.data),
  useLoading: () => store((s) => s.loading),
  useError: () => store((s) => s.error),
  useRefresh: () => store((s) => s.refresh),
})

// a zustand store around one slot + its guarded refresh: the whole store for
// every flat list (planned, tags, quick trails, requests, invites). an
// optional snapshot makes it offline-first: data seeds from disk before the
// first render and every data commit persists back
export const createSlotStore = <T>(
  initial: T,
  fetch: () => Promise<T>,
  snapshot?: Snapshot<T>,
) => {
  const guard = staleGuard()
  const store = create<SlotStore<T>>((set) => ({
    ...emptySlot(snapshot?.read() ?? initial),
    refresh: () => runSlot(set, fetch, guard),
  }))
  if (snapshot)
    store.subscribe((state, prev) => {
      if (state.data !== prev.data) snapshot.write(state.data)
    })
  registerReset(() => {
    guard.invalidate()
    // setState first: the persist subscriber runs, then clear wins
    store.setState(emptySlot(initial))
    snapshot?.clear()
  })
  return store
}
