/**
 * Reference Solution: Real-Time Content Listeners
 *
 * Demonstrates:
 * - Setting up client.listen() with GROQ filters
 * - Handling mutation events (create, update, delete)
 * - Maintaining an in-memory cache synchronized with the Content Lake
 * - Subscription cleanup and error handling
 * - TypeScript types for listener events
 */

import { createClient, type SanityDocument } from "@sanity/client"
import type { Subscription } from "rxjs"

// === Client Setup ===

const client = createClient({
  projectId: "your-project-id",
  dataset: "production",
  apiVersion: "2024-01-01",
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Listeners require the real-time API
})

// === Types ===

interface Post extends SanityDocument {
  _type: "post"
  title: string
  slug: { current: string }
  publishedAt: string
}

interface MutationEvent {
  type: "mutation"
  documentId: string
  transition: "update" | "appear" | "disappear"
  result?: SanityDocument
  previousRev?: string
  resultRev?: string
}

interface ReconnectEvent {
  type: "reconnect"
}

type ListenerEvent = MutationEvent | ReconnectEvent

// === In-Memory Cache ===

class ContentCache<T extends SanityDocument> {
  private documents = new Map<string, T>()

  getAll(): T[] {
    return Array.from(this.documents.values())
  }

  get(id: string): T | undefined {
    return this.documents.get(id)
  }

  set(id: string, doc: T): void {
    this.documents.set(id, doc)
  }

  delete(id: string): boolean {
    return this.documents.delete(id)
  }

  get size(): number {
    return this.documents.size
  }
}

// === Listener Setup ===

/**
 * Creates a real-time listener that keeps a local cache in sync with
 * documents matching a GROQ filter.
 *
 * Returns an object with the cache and an unsubscribe function.
 */
export async function createRealtimeSync<T extends SanityDocument>(options: {
  filter: string
  params?: Record<string, unknown>
  onUpdate?: (event: MutationEvent) => void
  onError?: (error: Error) => void
}): Promise<{
  cache: ContentCache<T>
  unsubscribe: () => void
}> {
  const cache = new ContentCache<T>()

  // 1. Initial fetch — populate cache with current data
  const initialDocs = await client.fetch<T[]>(
    `*[${options.filter}]`,
    options.params || {},
  )

  for (const doc of initialDocs) {
    cache.set(doc._id, doc)
  }

  // 2. Start listener — watch for mutations matching the filter
  const subscription: Subscription = client
    .listen<T>(
      `*[${options.filter}]`,
      options.params || {},
      {
        includeResult: true,
        includePreviousRevision: false,
        visibility: "query",
      },
    )
    .subscribe({
      next: (event) => {
        if (event.type === "reconnect") {
          // The listener reconnected — could refetch to ensure consistency
          return
        }

        if (event.type === "mutation") {
          const mutation = event as unknown as MutationEvent

          switch (mutation.transition) {
            case "appear": {
              // New document matches the filter
              if (mutation.result) {
                cache.set(mutation.documentId, mutation.result as T)
              }
              break
            }
            case "update": {
              // Existing document was modified
              if (mutation.result) {
                cache.set(mutation.documentId, mutation.result as T)
              }
              break
            }
            case "disappear": {
              // Document no longer matches the filter (deleted or updated)
              cache.delete(mutation.documentId)
              break
            }
          }

          options.onUpdate?.(mutation)
        }
      },
      error: (err) => {
        options.onError?.(
          err instanceof Error ? err : new Error(String(err)),
        )
      },
    })

  // 3. Return cache and cleanup function
  return {
    cache,
    unsubscribe: () => subscription.unsubscribe(),
  }
}

// === Usage Example ===

async function main() {
  const { cache, unsubscribe } = await createRealtimeSync<Post>({
    filter: '_type == "post"',
    onUpdate: (event) => {
      console.log(
        `Post ${event.documentId} ${event.transition}:`,
        event.result?.title,
      )
    },
    onError: (error) => {
      console.error("Listener error:", error.message)
    },
  })

  console.log(`Watching ${cache.size} posts for changes...`)

  // Clean up when done (e.g., on process exit)
  process.on("SIGINT", () => {
    unsubscribe()
    process.exit(0)
  })
}
