import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query'
import {
  fetchChatThread,
  getAccountById,
  getConversations,
  getMastodonConversations,
  getMastodonConversationThread,
  getMutualFollowers,
  type MastodonConversation,
} from 'src/lib/api'
import type { Account } from 'src/lib/api-types'
import {
  type ChatConversationRow,
  type ChatThreadEnvelope,
  normalizeMastodonConversation,
  normalizeMastodonThread,
  normalizePixelfedConversation,
} from 'src/lib/chat-shape'
import { AccountStore } from 'src/state/accountStore'
import { queryKeys } from './keys'

// Phase C §4.4 of the Mastodon follow-up plan: the chat queries dispatch by
// the active account's type and run each protocol's wire payload through
// `chat-shape` before handing it to the screens. The Pixelfed branch keeps
// its existing behaviour wrapped in the normalizer; the Mastodon branch
// fetches via `/api/v1/conversations` + `/api/v1/statuses/:id/context` and
// produces the same shapes the screens consumed before.

type ConversationsPage = {
  data: ChatConversationRow[]
  nextPage: string | null
  prevPage: string | null
}

export const chatQueries = {
  conversations: () =>
    infiniteQueryOptions({
      queryKey: queryKeys.chats.conversations(),
      queryFn: async ({ pageParam }): Promise<ConversationsPage> => {
        const account = AccountStore.activeAccountOrThrow()
        const selfId = String(account.userId)
        if (account.type === 'mastodon') {
          const page = await getMastodonConversations({
            pageParam: pageParam as string | false,
          })
          return {
            data: page.data.map((c: MastodonConversation) =>
              normalizeMastodonConversation(c, selfId)
            ),
            nextPage: page.nextPage,
            prevPage: page.prevPage,
          }
        }
        const page = (await getConversations({
          pageParam: pageParam as string | false,
        })) as { data: any[]; nextPage: string | null; prevPage: string | null }
        return {
          data: (page.data ?? []).map((c: any) =>
            normalizePixelfedConversation(c, selfId)
          ),
          nextPage: page.nextPage,
          prevPage: page.prevPage,
        }
      },
      initialPageParam: false as string | false,
      staleTime: 60 * 1000,
      gcTime: 30 * 60 * 1000,
      getNextPageParam: (lastPage: ConversationsPage) => lastPage.nextPage ?? undefined,
      getPreviousPageParam: (lastPage: ConversationsPage) =>
        lastPage.prevPage ?? undefined,
    }),

  thread: (id: string) =>
    queryOptions({
      queryKey: queryKeys.chats.thread(id),
      queryFn: async (): Promise<ChatThreadEnvelope | unknown> => {
        const account = AccountStore.activeAccountOrThrow()
        if (account.type === 'mastodon') {
          const selfId = String(account.userId)
          // The route param `id` may be a Mastodon `Conversation.id`
          // (existing thread) or — for the new-message flow from
          // `chats/search.tsx` — an Account id that hasn't been
          // converted to a conversation yet. Try both, in that order.
          //
          // On the conversation hit: read the anchor `last_status.id`
          // and fetch context. On the account-id fallback: synthesize an
          // empty thread envelope with the recipient as participant so
          // the screen can render the composer without messages — the
          // first send creates the conversation server-side.
          const row = await findMastodonConversationById(id)
          if (row?.last_status?.id) {
            const { context, anchor } = await getMastodonConversationThread(
              row.last_status.id
            )
            const participant = row.accounts[0] ?? row.last_status.account
            return normalizeMastodonThread(context, anchor, participant, selfId)
          }

          // Account-id fallback. `getAccountById` 404s for non-numeric /
          // non-existent ids — return the bare-empty envelope on failure.
          let recipient: Account | null = null
          try {
            recipient = (await getAccountById(id)) as Account
          } catch {
            recipient = null
          }
          return {
            id: recipient?.id ?? id,
            name: recipient?.display_name ?? null,
            username: recipient?.username ?? '',
            acct: recipient?.acct,
            avatar: recipient?.avatar ?? '',
            messages: [],
          } satisfies ChatThreadEnvelope
        }
        // Pixelfed thread shape predates `chat-shape`; the conversation
        // screen reads it directly so we forward as-is.
        return await fetchChatThread(id)
      },
      staleTime: 30 * 1000,
      gcTime: 10 * 60 * 1000,
    }),

  // Pixelfed-only "compose new message" candidate list. Mastodon's compose
  // flow uses the user-search API instead — handled at the screen level.
  composeMutuals: () =>
    queryOptions({
      queryKey: queryKeys.chats.composeMutuals(),
      queryFn: getMutualFollowers,
      staleTime: 5 * 60 * 1000,
      gcTime: 60 * 60 * 1000,
    }),
}

async function findMastodonConversationById(
  id: string
): Promise<MastodonConversation | null> {
  const page = await getMastodonConversations({})
  return page.data.find((c: MastodonConversation) => c.id === id) ?? null
}
