// In-memory MMKV stand-in for jest. Matches the subset of `react-native-mmkv`
// that production code touches: get/set/remove/delete/clearAll/getAllKeys plus
// the value-change listener channel storage.ts wires up for the theme prefs.
//
// `createMMKV({ id })` is the entry point production code uses, so the helper
// also returns a per-id instance registry — pass `mmkv.getOrCreate(id)` from
// inside the `jest.mock` factory and every `createMMKV({ id: 'foo' })` call
// converges on the same instance, mirroring native MMKV's id-keyed shared
// state.
//
// Variables consumed inside `jest.mock` factories must be prefixed `mock*`
// (jest's hoisting allow-list), so callers should bind the returned registry
// as `const mockMmkv = createMmkvInstances()` at the top of the test file.

type Listener = (key: string) => void

export type InMemoryMmkv = {
  set: (key: string, value: string | number | boolean) => void
  getString: (key: string) => string | undefined
  getNumber: (key: string) => number | undefined
  getBoolean: (key: string) => boolean | undefined
  remove: (key: string) => void
  delete: (key: string) => void
  clearAll: () => void
  getAllKeys: () => string[]
  addOnValueChangedListener: (fn: Listener) => { remove: () => void }
  __reset: () => void
  __dump: () => Record<string, string | number | boolean>
}

export function createInMemoryMmkv(opts: { compareBeforeSet?: boolean } = {}): InMemoryMmkv {
  const store = new Map<string, string | number | boolean>()
  const listeners = new Set<Listener>()
  const compareBeforeSet = opts.compareBeforeSet ?? false
  const drop = (key: string): boolean => {
    if (!store.has(key)) return false
    store.delete(key)
    listeners.forEach((fn) => fn(key))
    return true
  }
  return {
    set(key, value) {
      if (compareBeforeSet && store.get(key) === value) return
      store.set(key, value)
      listeners.forEach((fn) => fn(key))
    },
    getString: (key) => {
      const v = store.get(key)
      return typeof v === 'string' ? v : undefined
    },
    getNumber: (key) => {
      const v = store.get(key)
      return typeof v === 'number' ? v : undefined
    },
    getBoolean: (key) => {
      const v = store.get(key)
      return typeof v === 'boolean' ? v : undefined
    },
    remove: drop,
    delete: drop,
    clearAll() {
      const keys = Array.from(store.keys())
      store.clear()
      for (const k of keys) listeners.forEach((fn) => fn(k))
    },
    getAllKeys: () => Array.from(store.keys()),
    addOnValueChangedListener(fn) {
      listeners.add(fn)
      return { remove: () => listeners.delete(fn) }
    },
    __reset() {
      store.clear()
      listeners.clear()
    },
    __dump: () => Object.fromEntries(store.entries()),
  }
}

export type MmkvInstanceRegistry = {
  getOrCreate: (id: string, opts?: { compareBeforeSet?: boolean }) => InMemoryMmkv
  get: (id: string) => InMemoryMmkv | undefined
  reset: () => void
}

/**
 * Per-id MMKV instance registry. Use from inside a `jest.mock(
 * 'react-native-mmkv', () => ...)` factory:
 *
 *   const mockMmkv = createMmkvInstances()
 *   jest.mock('react-native-mmkv', () => ({
 *     createMMKV: ({ id, compareBeforeSet }) =>
 *       mockMmkv.getOrCreate(id, { compareBeforeSet }),
 *   }))
 *
 * `reset()` clears every instance's contents without losing the instance
 * identity — caches inside production code (e.g. AccountStore's
 * `_listSnapshot`) re-read from the same instance after a reset.
 */
export function createMmkvInstances(): MmkvInstanceRegistry {
  const instances = new Map<string, InMemoryMmkv>()
  return {
    getOrCreate(id, opts) {
      const cached = instances.get(id)
      if (cached) return cached
      const fresh = createInMemoryMmkv(opts)
      instances.set(id, fresh)
      return fresh
    },
    get: (id) => instances.get(id),
    reset() {
      for (const m of instances.values()) m.__reset()
    },
  }
}
