// monotonic tokens that let overlapping async runs detect they've been
// superseded: claim() before starting, isCurrent(token) before committing.
// the one stale-response mechanism every resource machine shares

export type StaleGuard = {
  claim: () => number
  isCurrent: (token: number) => boolean
  // mark every in-flight run stale (store reset, cache wipe)
  invalidate: () => void
}

export const staleGuard = (): StaleGuard => {
  let seq = 0
  return {
    claim: () => ++seq,
    isCurrent: (token) => token === seq,
    invalidate: () => {
      seq++
    },
  }
}

// per-key guards for keyed caches: key A's refresh never invalidates key B's
export type KeyedStaleGuard = {
  claim: (key: string) => number
  isCurrent: (key: string, token: number) => boolean
  invalidate: () => void
}

export const keyedStaleGuard = (): KeyedStaleGuard => {
  const seqs = new Map<string, number>()
  return {
    claim: (key) => {
      const next = (seqs.get(key) ?? 0) + 1
      seqs.set(key, next)
      return next
    },
    isCurrent: (key, token) => seqs.get(key) === token,
    invalidate: () => {
      for (const key of seqs.keys()) seqs.set(key, seqs.get(key)! + 1)
    },
  }
}
