import { describe, it, expect, vi } from 'vitest'
import { createReconciler } from '../src/renderer/src/lib/reconcile'

/** A promise whose resolution the test controls. */
function deferred<T>(): {
  promise: Promise<T>
  resolve: (v: T) => void
  reject: (e: unknown) => void
} {
  let resolve!: (v: T) => void
  let reject!: (e: unknown) => void
  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })
  return { promise, resolve, reject }
}

const flush = () => new Promise((r) => setTimeout(r, 0))

describe('createReconciler (L142)', () => {
  it('applies the response of a single call', async () => {
    const reconcile = createReconciler()
    const apply = vi.fn()
    const d = deferred<string[]>()
    reconcile('k', d.promise, apply, vi.fn())
    d.resolve(['a'])
    await flush()
    expect(apply).toHaveBeenCalledWith(['a'])
  })

  it('drops a stale response when a newer call was issued on the same key', async () => {
    const reconcile = createReconciler()
    const applied: string[][] = []
    const apply = (v: string[]) => applied.push(v)
    const first = deferred<string[]>()
    const second = deferred<string[]>()
    reconcile('k', first.promise, apply, vi.fn())
    reconcile('k', second.promise, apply, vi.fn())
    // Even resolving in order, only the newest call's response is applied —
    // the first would transiently roll back the second's optimistic update.
    first.resolve(['a'])
    second.resolve(['a', 'b'])
    await flush()
    expect(applied).toEqual([['a', 'b']])
  })

  it('drops an out-of-order late response entirely', async () => {
    const reconcile = createReconciler()
    const applied: string[][] = []
    const first = deferred<string[]>()
    const second = deferred<string[]>()
    reconcile('k', first.promise, (v) => applied.push(v), vi.fn())
    reconcile('k', second.promise, (v) => applied.push(v), vi.fn())
    second.resolve(['a', 'b'])
    await flush()
    first.resolve(['a']) // resolves after the newer response was applied
    await flush()
    expect(applied).toEqual([['a', 'b']])
  })

  it('keys are independent — a call on one key never suppresses another', async () => {
    const reconcile = createReconciler()
    const applied: Record<string, number[]> = {}
    const a = deferred<number[]>()
    const b = deferred<number[]>()
    reconcile('a', a.promise, (v) => (applied.a = v), vi.fn())
    reconcile('b', b.promise, (v) => (applied.b = v), vi.fn())
    a.resolve([1])
    b.resolve([2])
    await flush()
    expect(applied).toEqual({ a: [1], b: [2] })
  })

  it('routes rejection to onError and never to apply', async () => {
    const reconcile = createReconciler()
    const apply = vi.fn()
    const onError = vi.fn()
    const d = deferred<string[]>()
    reconcile('k', d.promise, apply, onError)
    d.reject(new Error('ipc failed'))
    await flush()
    expect(apply).not.toHaveBeenCalled()
    expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'ipc failed' }))
  })

  it('a rejected older call does not block applying the newer response', async () => {
    const reconcile = createReconciler()
    const applied: string[][] = []
    const onError = vi.fn()
    const first = deferred<string[]>()
    const second = deferred<string[]>()
    reconcile('k', first.promise, (v) => applied.push(v), onError)
    reconcile('k', second.promise, (v) => applied.push(v), onError)
    first.reject(new Error('boom'))
    second.resolve(['b'])
    await flush()
    expect(applied).toEqual([['b']])
    expect(onError).toHaveBeenCalledTimes(1)
  })
})
