import { type QueryClient, type QueryKey } from '@tanstack/react-query'

const DEFAULT_CHANNEL = 'platform-query'

export interface QueryBroadcastChannel {
  postMessage(message: unknown): void
  close?(): void
  onmessage: ((event: MessageEvent<unknown>) => void) | null
}

export interface QueryBroadcaster {
  publishKeys(keys: QueryKey[]): void
  start(): () => void
}

interface BroadcastMessage {
  type: 'query:broadcast'
  origin: string
  buster: string
  keys: QueryKey[]
}

export interface CreateQueryBroadcasterOptions {
  channel?: string
  buster?: string
  createChannel?: (channelName: string) => QueryBroadcastChannel | null
}

function defaultCreateChannel(channelName: string): QueryBroadcastChannel | null {
  if (typeof BroadcastChannel === 'undefined') {
    return null
  }

  return new BroadcastChannel(channelName)
}

function createOrigin(): string {
  return `${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`
}

function isQueryBroadcastMessage(value: unknown): value is BroadcastMessage {
  if (typeof value !== 'object' || value === null) {
    return false
  }

  const candidate = value as Partial<BroadcastMessage>
  return (
    candidate.type === 'query:broadcast' &&
    typeof candidate.origin === 'string' &&
    typeof candidate.buster === 'string' &&
    Array.isArray(candidate.keys)
  )
}

export function createQueryBroadcaster(
  queryClient: QueryClient,
  options: CreateQueryBroadcasterOptions = {},
): QueryBroadcaster {
  const channelName = options.channel ?? DEFAULT_CHANNEL
  const buster = options.buster ?? ''
  const createChannel = options.createChannel ?? defaultCreateChannel
  const origin = createOrigin()
  let channel: QueryBroadcastChannel | null = null

  const cleanup = (): void => {
    if (channel === null) {
      return
    }

    channel.onmessage = null
    channel.close?.()
    channel = null
  }

  return {
    publishKeys(keys) {
      channel?.postMessage({
        type: 'query:broadcast',
        origin,
        buster,
        keys,
      } satisfies BroadcastMessage)
    },
    start() {
      if (channel !== null) {
        return cleanup
      }

      const nextChannel = createChannel(channelName)
      if (nextChannel === null) {
        return () => undefined
      }

      nextChannel.onmessage = (event) => {
        if (!isQueryBroadcastMessage(event.data)) {
          return
        }

        if (event.data.origin === origin || event.data.buster !== buster) {
          return
        }

        for (const queryKey of event.data.keys) {
          void queryClient.invalidateQueries({ queryKey })
        }
      }

      channel = nextChannel
      return cleanup
    },
  }
}
