/**
 * Reference Solution: GROQ Joins and References
 *
 * Demonstrates:
 * - Following single references with ->
 * - Resolving arrays of references
 * - Nested/reverse joins using parent scope (^)
 * - The references() function
 * - Building rich nested responses
 */

import { createClient } from "@sanity/client"

// ---------- Client setup ----------

const client = createClient({
  projectId: "your-project-id",
  dataset: "production",
  apiVersion: "2024-01-01",
  useCdn: true,
})

// ---------- Types ----------

interface SanityImage {
  _type: "image"
  asset: {
    _ref: string
    _type: "reference"
  }
}

interface Author {
  _id: string
  _type: "author"
  name: string
  bio: unknown[] // Portable Text blocks
  image: SanityImage | null
}

interface Category {
  _id: string
  _type: "category"
  title: string
  slug: { current: string }
}

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

// ---------- Result types for each query ----------

/** Result of resolving a post with its author (single reference) */
interface PostWithAuthor {
  _id: string
  title: string
  slug: { current: string }
  publishedAt: string
  author: {
    name: string
    bio: unknown[]
    image: SanityImage | null
  } | null
}

/** Result of resolving a post with its categories (array of references) */
interface PostWithCategories {
  _id: string
  title: string
  categories: Array<{
    _id: string
    title: string
  }>
}

/** Result of resolving an author with their latest posts (nested/reverse join) */
interface AuthorWithLatestPosts {
  _id: string
  name: string
  bio: unknown[]
  image: SanityImage | null
  latestPosts: Array<{
    _id: string
    title: string
    slug: { current: string }
    publishedAt: string
  }>
}

/** Result of finding all documents that reference a specific document */
interface ReferencingDocument {
  _id: string
  _type: string
  title: string
}

/** Result of a full post with all references resolved */
interface FullPost {
  _id: string
  title: string
  slug: { current: string }
  publishedAt: string
  author: {
    _id: string
    name: string
    image: SanityImage | null
  } | null
  categories: Array<{
    _id: string
    title: string
  }>
}

// ---------- 1. Follow a single reference with -> ----------
// The -> operator dereferences a reference field, replacing the
// { _ref: "abc123" } stub with the full referenced document.

async function fetchPostWithAuthor(
  postSlug: string
): Promise<PostWithAuthor | null> {
  const query = `
    *[_type == "post" && slug.current == $postSlug][0]{
      _id,
      title,
      slug,
      publishedAt,
      // Dereference the author and pick specific fields
      author->{
        name,
        bio,
        image
      }
    }
  `

  return client.fetch<PostWithAuthor | null>(query, { postSlug })
}

// ---------- 2. Follow an array of references ----------
// When a field is an array of references, use []-> to dereference
// every item in the array in one go.

async function fetchPostWithCategories(
  postSlug: string
): Promise<PostWithCategories | null> {
  const query = `
    *[_type == "post" && slug.current == $postSlug][0]{
      _id,
      title,
      // Dereference each category reference in the array
      "categories": categories[]->{
        _id,
        title
      }
    }
  `

  return client.fetch<PostWithCategories | null>(query, { postSlug })
}

// ---------- 3. Nested join with reverse reference ----------
// Combine -> with a sub-query that uses references() and ^._id
// to build a rich nested response in a single round trip.

async function fetchAuthorWithLatestPosts(
  authorSlug: string
): Promise<AuthorWithLatestPosts | null> {
  const query = `
    *[_type == "author" && slug.current == $authorSlug][0]{
      _id,
      name,
      bio,
      image,
      // Reverse join: find posts that reference this author.
      // ^._id refers to the _id of the outer (parent) document.
      "latestPosts": *[_type == "post" && references(^._id)] | order(publishedAt desc)[0...5]{
        _id,
        title,
        slug,
        publishedAt
      }
    }
  `

  return client.fetch<AuthorWithLatestPosts | null>(query, { authorSlug })
}

// ---------- 4. The references() function ----------
// references(id) matches any document that contains a reference
// to the given document ID, at any depth in its tree.

async function fetchDocumentsReferencingId(
  documentId: string
): Promise<ReferencingDocument[]> {
  const query = `
    *[references($documentId)]{
      _id,
      _type,
      title
    }
  `

  return client.fetch<ReferencingDocument[]>(query, { documentId })
}

// ---------- 5. Reverse reference with parent scope (^) ----------
// Use ^ to access the parent scope in nested projections.
// This enables "give me X and, for each X, find related Y" patterns.

async function fetchCategoriesWithPostCount(): Promise<
  Array<{
    _id: string
    title: string
    postCount: number
  }>
> {
  const query = `
    *[_type == "category"]{
      _id,
      title,
      // Count how many posts reference this category.
      // ^._id is the current category's _id.
      "postCount": count(*[_type == "post" && references(^._id)])
    } | order(postCount desc)
  `

  return client.fetch(query)
}

// ---------- 6. Combining multiple join patterns ----------
// A single query can mix all patterns: forward references,
// array dereferencing, and reverse joins.

async function fetchFullPost(postSlug: string): Promise<FullPost | null> {
  const query = `
    *[_type == "post" && slug.current == $postSlug][0]{
      _id,
      title,
      slug,
      publishedAt,
      // Single reference join
      author->{
        _id,
        name,
        image
      },
      // Array of references join
      "categories": categories[]->{
        _id,
        title
      }
    }
  `

  return client.fetch<FullPost | null>(query, { postSlug })
}

// ---------- Usage examples ----------

async function main() {
  // 1. Get a post with its author resolved
  const postWithAuthor = await fetchPostWithAuthor("my-first-post")
  console.log("Post author:", postWithAuthor?.author?.name)

  // 2. Get a post with its categories resolved
  const postWithCategories = await fetchPostWithCategories("my-first-post")
  console.log(
    "Categories:",
    postWithCategories?.categories.map((c) => c.title)
  )

  // 3. Get an author with their 5 latest posts (nested reverse join)
  const authorWithPosts = await fetchAuthorWithLatestPosts("jane-doe")
  console.log(
    "Latest posts:",
    authorWithPosts?.latestPosts.map((p) => p.title)
  )

  // 4. Find everything that references a specific document
  const referencingDocs = await fetchDocumentsReferencingId("author-abc123")
  console.log("Documents referencing author:", referencingDocs.length)

  // 5. Get categories with their post counts (reverse reference)
  const categoriesWithCounts = await fetchCategoriesWithPostCount()
  console.log(
    "Categories by post count:",
    categoriesWithCounts.map((c) => `${c.title}: ${c.postCount}`)
  )

  // 6. Get a fully resolved post (combines all patterns)
  const fullPost = await fetchFullPost("my-first-post")
  console.log("Full post:", fullPost?.title, "by", fullPost?.author?.name)
}

main()
