import { QueryClient } from '@tanstack/react-query'
import { describe, expect, it, vi } from 'vitest'
import { createQueryBroadcaster, type QueryBroadcastChannel } from './broadcast'

describe('broadcast', () => {
  it('does nothing when BroadcastChannel is unavailable', () => {
    const queryClient = new QueryClient()
    const broadcaster = createQueryBroadcaster(queryClient)
    const cleanup = broadcaster.start()

    expect(() => broadcaster.publishKeys([['cart']])).not.toThrow()
    expect(() => cleanup()).not.toThrow()
  })

  it('opens lazily on start, ignores self-originated messages, and cleans up', () => {
    const queryClient = new QueryClient()
    const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries')
    const postMessage = vi.fn()
    const close = vi.fn()
    const listenerRef: {
      current: ((event: MessageEvent<unknown>) => void) | null
    } = { current: null }
    const createChannel = vi.fn(
      (): QueryBroadcastChannel => ({
        postMessage,
        close,
        set onmessage(handler: ((event: MessageEvent<unknown>) => void) | null) {
          listenerRef.current = handler
        },
      }),
    )

    const broadcaster = createQueryBroadcaster(queryClient, {
      channel: 'query-react',
      buster: 'build-1',
      createChannel,
    })

    expect(createChannel).not.toHaveBeenCalled()

    const cleanup = broadcaster.start()

    expect(createChannel).toHaveBeenCalledTimes(1)

    broadcaster.publishKeys([['cart']])
    expect(postMessage).toHaveBeenCalledTimes(1)

    const receiveMessage = listenerRef.current
    expect(receiveMessage).not.toBeNull()
    if (receiveMessage === null) {
      throw new Error('expected broadcast listener to be registered')
    }

    receiveMessage({
      data: postMessage.mock.calls[0]?.[0],
    } as MessageEvent<unknown>)

    expect(invalidateQueries).not.toHaveBeenCalled()

    receiveMessage({
      data: {
        type: 'query:broadcast',
        origin: 'remote',
        buster: 'build-1',
        keys: [['cart']],
      },
    } as MessageEvent<unknown>)

    expect(invalidateQueries).toHaveBeenCalledTimes(1)
    cleanup()
    expect(close).toHaveBeenCalledTimes(1)
  })
})
