import {describe, expect, test} from 'bun:test'

import {createSlotStore} from '@/lib/async-slot'
import {type Snapshot} from '@/lib/snapshot-cache'
import {registerReset, resetAllStores} from '@/lib/store-reset'

const fakeSnapshot = <T>(
  initial?: T,
): Snapshot<T> & {
  current: () => T | undefined
} => {
  let value = initial
  return {
    read: () => value,
    write: (next) => {
      value = next
    },
    clear: () => {
      value = undefined
    },
    current: () => value,
  }
}

describe('createSlotStore', () => {
  test('refresh moves through loading to data', async () => {
    const store = createSlotStore<string[]>([], () =>
      Promise.resolve(['a']),
    )
    const refresh = store.getState().refresh()
    expect(store.getState().loading).toBe(true)
    await refresh
    expect(store.getState()).toMatchObject({
      data: ['a'],
      loading: false,
      error: null,
    })
  })

  test('keeps prior data while loading and on failure', async () => {
    let fail = false
    const store = createSlotStore<string[]>([], () =>
      fail
        ? Promise.reject(new Error('boom'))
        : Promise.resolve(['a']),
    )
    await store.getState().refresh()
    fail = true
    await store.getState().refresh()
    expect(store.getState().data).toEqual(['a'])
    expect(store.getState().error).not.toBeNull()
  })

  test('an older refresh cannot clobber a newer one', async () => {
    const resolvers: ((v: string[]) => void)[] = []
    const store = createSlotStore<string[]>(
      [],
      () => new Promise((resolve) => resolvers.push(resolve)),
    )
    const first = store.getState().refresh()
    const second = store.getState().refresh()
    resolvers[1]!(['new'])
    await second
    resolvers[0]!(['old'])
    await first
    expect(store.getState().data).toEqual(['new'])
  })

  test('resetAllStores wipes the slot back to its initial value', async () => {
    const store = createSlotStore<string[]>([], () =>
      Promise.resolve(['a']),
    )
    await store.getState().refresh()
    resetAllStores()
    expect(store.getState().data).toEqual([])
  })

  test('seeds data from a snapshot', () => {
    const store = createSlotStore<string[]>(
      [],
      () => Promise.resolve(['fresh']),
      fakeSnapshot(['cached']),
    )
    expect(store.getState().data).toEqual(['cached'])
    expect(store.getState().loading).toBe(false)
  })

  test('persists refreshed data to the snapshot', async () => {
    const snapshot = fakeSnapshot<string[]>()
    const store = createSlotStore<string[]>(
      [],
      () => Promise.resolve(['a']),
      snapshot,
    )
    await store.getState().refresh()
    expect(snapshot.current()).toEqual(['a'])
  })

  test('a failed refresh leaves the snapshot untouched', async () => {
    const snapshot = fakeSnapshot(['cached'])
    const store = createSlotStore<string[]>(
      [],
      () => Promise.reject(new Error('boom')),
      snapshot,
    )
    await store.getState().refresh()
    expect(snapshot.current()).toEqual(['cached'])
    expect(store.getState().data).toEqual(['cached'])
  })

  test('reset clears the snapshot', async () => {
    const snapshot = fakeSnapshot<string[]>()
    const store = createSlotStore<string[]>(
      [],
      () => Promise.resolve(['a']),
      snapshot,
    )
    await store.getState().refresh()
    resetAllStores()
    expect(snapshot.current()).toBeUndefined()
    expect(store.getState().data).toEqual([])
  })
})

describe('store-reset registry', () => {
  test('runs every registered reset', () => {
    let called = 0
    registerReset(() => {
      called++
    })
    resetAllStores()
    expect(called).toBe(1)
  })
})
