import ProfileHeader from '@components/profile/ProfileHeader'
import { Feather } from '@expo/vector-icons'
import { FlashList } from '@shopify/flash-list'
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import * as Clipboard from 'expo-clipboard'
import type { ErrorBoundaryProps } from 'expo-router'
import { router, useLocalSearchParams } from 'expo-router'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ActivityIndicator, Pressable, Share, StyleSheet } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { EmptyState } from 'src/components/common/EmptyState'
import { ErrorState } from 'src/components/common/ErrorState'
import { PostGridCell } from 'src/components/common/PostGridCell'
import { useScreenMenu } from 'src/components/menu'
import EventCard from 'src/components/post/EventCard'
import FeedPost from 'src/components/post/FeedPost'
import StoriesRail from 'src/components/stories/StoriesRail'
import { useBlockMutation } from 'src/hooks/mutations/useBlockMutation'
import { useFollowMutation } from 'src/hooks/mutations/useFollowMutation'
import { useMuteMutation } from 'src/hooks/mutations/useMuteMutation'
import { useShareMutation } from 'src/hooks/mutations/useShareMutation'
import { PlaybackScope } from 'src/hooks/playback/PlaybackScope'
import { resolveUrlViaSearch } from 'src/lib/api'
import type { Status } from 'src/lib/api-types'
import { useFeature } from 'src/lib/features'
import { haptic } from 'src/lib/haptics'
import { ICON } from 'src/lib/icons'
import { type PublicProfileTab, profileChrome } from 'src/lib/protocol/chrome'
import { accountQueries } from 'src/lib/queries/accounts'
import { postQueries } from 'src/lib/queries/posts'
import { isSyntheticRemoteId, RemoteOutboxError } from 'src/lib/remote-outbox'
import { useActiveProfile, useAuth } from 'src/state/AuthProvider'
import { Text, useTheme, useToastController, View, YStack } from 'tamagui'

export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
  return <ErrorState message={error.message} onRetry={retry} />
}

// D1a — the synthetic post is rendered through the regular `FeedPost` for
// visual parity, then a full-bleed Pressable overlay swallows every tap so
// likes/boosts/comments can't fire against the synthetic id. Tap action:
// resolve the canonical AP url through the home instance via
// `/api/v2/search?resolve=1` and navigate to the resulting home-instance
// post detail, where the user can interact normally. Resolve failure surfaces
// a toast but is otherwise non-fatal — they can still scroll past.
function RemoteOutboxPostCell({ post, user }: { post: Status; user: any }) {
  const toast = useToastController()
  const [resolving, setResolving] = useState(false)

  const onResolve = useCallback(async () => {
    if (resolving) return
    setResolving(true)
    try {
      const remoteUrl = post.uri || post.url || ''
      if (!remoteUrl) throw new Error('no url')
      const res = (await resolveUrlViaSearch(remoteUrl)) as {
        statuses?: Array<{ id: string }>
      }
      const id = res?.statuses?.[0]?.id
      if (!id) throw new Error('no match')
      router.push(`/post/${id}`)
    } catch {
      // `preset: 'none'` keeps this neutral rather than red — this is a
      // soft "couldn't resolve to a local post" outcome, not a failure of
      // user intent. Cross-instance resolution missing is expected, and
      // GlobalToast falls through to the default theme on `'none'`.
      toast.show("Couldn't open this post on your server", {
        preset: 'none',
        duration: 2500,
      })
    } finally {
      setResolving(false)
    }
  }, [post.uri, post.url, resolving, toast])

  return (
    <View>
      <FeedPost
        post={post}
        user={user}
        onOpenComments={() => onResolve()}
        onDeletePost={() => {}}
        onShare={() => onResolve()}
        disableReadMore
      />
      <Pressable
        accessibilityLabel="Open on your server"
        accessibilityRole="button"
        style={StyleSheet.absoluteFill}
        onPress={onResolve}
      />
      {resolving ? (
        <View
          position="absolute"
          top={0}
          left={0}
          right={0}
          bottom={0}
          justifyContent="center"
          alignItems="center"
          backgroundColor="rgba(0,0,0,0.15)"
        >
          <ActivityIndicator />
        </View>
      ) : null}
    </View>
  )
}

export default function ProfileScreenRoute() {
  return (
    <PlaybackScope name="profile:public">
      <ProfileScreen />
    </PlaybackScope>
  )
}

function ProfileScreen() {
  const {
    id,
    byUsername,
    tab: initialTab,
  } = useLocalSearchParams<{
    id: string
    byUsername?: string
    tab?: string
  }>()
  const toastController = useToastController()
  const theme = useTheme()
  // Prefer the registry's userId (single source of truth) over the cached
  // blob's `id` so a corrupted cached blob can't mis-flag the self path.
  const { profile: selfUser, summary } = useActiveProfile()
  const selfId = summary?.userId ?? selfUser?.id ?? ''
  const accountType = useAuth().user?.type ?? 'pixelfed'
  const chrome = useMemo(() => profileChrome(accountType), [accountType])
  const [activeTab, setActiveTab] = useState<PublicProfileTab>(
    chrome.isPublicTab(initialTab) ? initialTab : 'posts'
  )

  const onOpenComments = useCallback(
    (postId: string) => router.push(`/post/${postId}`),
    []
  )
  const { handleShare } = useShareMutation()
  const onPostShare = useCallback(
    (postId: string, state: boolean) => handleShare(postId, state),
    [handleShare]
  )

  const showsGrid = chrome.isGridTab(activeTab)
  const RenderItem = useCallback(
    ({ item }: { item: any }) => {
      if (isSyntheticRemoteId(item?.id)) {
        // D1a — synthetic AP-outbox post. Tap actions (like, boost, reply,
        // open comments) all need a home-instance status id, which we don't
        // have until `/api/v2/search?resolve=1` returns one. Render the
        // visual through the regular `FeedPost`, then overlay a full-bleed
        // Pressable that swallows every tap and routes through the resolver.
        return <RemoteOutboxPostCell post={item} user={selfUser as any} />
      }
      if (!showsGrid) {
        return (
          <FeedPost
            post={item}
            user={selfUser as any}
            onOpenComments={() => onOpenComments(item.id)}
            onDeletePost={() => {}}
            onShare={() => onPostShare(item.id, item.reblogged === true)}
          />
        )
      }
      if (!item?.media_attachments?.length) return null
      return <PostGridCell post={item} />
    },
    [showsGrid, selfUser, onOpenComments, onPostShare]
  )

  const EmptyFeed = () => {
    if (!isFetched || isFetching) {
      return (
        <YStack flex={1} justifyContent="center" alignItems="center" gap="$5">
          <ActivityIndicator />
        </YStack>
      )
    }

    // D1a — outbox fallback is in flight after the home feed settled empty.
    // Show a spinner instead of the "no posts" copy so the empty-then-filled
    // transition doesn't flicker through the wrong state.
    if (shouldUseOutboxFallback && (outboxQuery.isLoading || outboxQuery.isFetching)) {
      return (
        <YStack flex={1} justifyContent="center" alignItems="center" gap="$5">
          <ActivityIndicator />
        </YStack>
      )
    }

    // D1a — outbox fetch was rejected (403 sigs-required, 404, network).
    if (outboxBlocked) {
      return (
        <YStack flexGrow={1} justifyContent="center" alignItems="center" gap="$4" px="$6">
          <View
            p="$6"
            borderWidth={2}
            borderColor={theme.borderColor.val}
            borderRadius={100}
          >
            <Feather name="cloud-off" size={ICON.xl} color={theme.colorTertiary.val} />
          </View>
          <Text fontSize="$7" color={theme.color.val} textAlign="center">
            Posts unavailable
          </Text>
          <Text fontSize="$4" color={theme.colorTertiary.val} textAlign="center">
            This server hasn't shared any of @{user?.acct}'s posts yet, and we couldn't
            read them directly either.
          </Text>
        </YStack>
      )
    }

    if (!isFetching && !user?.id) {
      return (
        <YStack flexGrow={1} justifyContent="center" alignItems="center" gap="$5">
          <View
            p="$6"
            borderWidth={2}
            borderColor={theme.borderColor.val}
            borderRadius={100}
          >
            <Feather
              name="alert-triangle"
              size={ICON.xl}
              color={theme.colorTertiary.val}
            />
          </View>
          <Text fontSize="$7" color={theme.color.val} textAlign="center">
            Account not found
          </Text>
        </YStack>
      )
    }
    return (
      <View flex={1} alignItems="center" justifyContent="center">
        <YStack
          h="100%"
          flexGrow={1}
          justifyContent="center"
          alignItems="center"
          gap="$5"
        >
          {user?.locked && !relationship?.following ? (
            <>
              <View
                p="$6"
                borderWidth={2}
                borderColor={theme.borderColor.val}
                borderRadius={100}
              >
                <Feather name="lock" size={ICON.xl} color={theme.colorTertiary.val} />
              </View>
              <YStack justifyContent="center" alignItems="center" gap="$2" px="$5">
                <Text fontSize="$8" color={theme.color.val}>
                  This account is private
                </Text>
                <Text fontSize="$5" color={theme.colorTertiary.val}>
                  Follow this account to see their photos and videos.
                </Text>
              </YStack>
            </>
          ) : (
            <View flexGrow={1} alignItems="center" justifyContent="center" gap="$4">
              <View
                p="$6"
                borderWidth={2}
                borderColor={theme.borderColor.val}
                borderRadius={100}
              >
                <Feather
                  name={isCommunityActor ? 'calendar' : 'camera'}
                  size={ICON.empty}
                  color={theme.colorTertiary.val}
                />
              </View>
              <Text fontSize="$9" color={theme.colorTertiary.val}>
                {isCommunityActor ? 'No Events Yet' : 'No Posts Yet'}
              </Text>
            </View>
          )}
        </YStack>
      </View>
    )
  }

  const { follow } = useFollowMutation()
  const { block } = useBlockMutation()
  const { mute } = useMuteMutation()

  const menuGotoLink = async (action: any) => {
    if (action === 'report') {
      router.push({ pathname: '/profile/report/[id]', params: { id: userId } })
    }

    if (action === 'copyurl') {
      Clipboard.setStringAsync(user.url)
      toastController.show('Profile copied to clipboard', {
        preset: 'success',
        duration: 2500,
      })
    }

    if (action === 'share') {
      try {
        const _result = await Share.share({
          message: user.url,
        })
      } catch (error: any) {
        toastController.show(
          error?.message || 'share sheet failed to open and error had no message',
          { preset: 'error' }
        )
      }
    }

    if (action === 'about') {
      router.push(`/profile/about/${userId}`)
    }
  }

  const handleBlock = () => {
    if (!userId) return
    block({
      targetId: userId,
      targetHandle: user?.acct,
      account: user,
      action: 'block',
    })
  }

  const handleUnblock = () => {
    if (!userId) return
    block({
      targetId: userId,
      targetHandle: user?.acct,
      action: 'unblock',
    })
  }

  const handleMute = () => {
    if (!userId) return
    mute({
      targetId: userId,
      targetHandle: user?.acct,
      account: user,
      action: 'mute',
    })
  }

  const handleUnmute = () => {
    if (!userId) return
    mute({
      targetId: userId,
      targetHandle: user?.acct,
      action: 'unmute',
    })
  }

  const handleFollow = () => {
    if (!userId) return
    follow({
      targetId: userId,
      targetHandle: user?.acct,
      action: 'follow',
      isLocked: !!user?.locked,
    })
  }

  const handleUnfollow = () => {
    if (!userId) return
    follow({
      targetId: userId,
      targetHandle: user?.acct,
      action: 'unfollow',
    })
  }

  const handleCancelFollowRequest = () => {
    handleUnfollow()
  }

  const {
    data: user,
    error: userError,
    refetch: refetchUser,
  } = useQuery(
    (byUsername !== undefined && id === '0'
      ? accountQueries.byHandle(byUsername)
      : accountQueries.byId(id)) as ReturnType<typeof accountQueries.byId>
  )

  const userId = user?.id
  const isSelf = !!selfId && selfId === userId

  const { data: relationship } = useQuery({
    ...accountQueries.relationship(userId as string),
    enabled: !!userId && !userError,
  })

  const { data: mutuals } = useQuery({
    ...accountQueries.mutualFollowing(userId as string),
    enabled: !!relationship,
  })

  const RenderHeader = useCallback(
    () => (
      <>
        {user?.id ? <StoriesRail scopedToPid={String(user.id)} /> : null}
        <ProfileHeader profile={user} relationship={relationship} mutuals={mutuals} />
      </>
    ),
    [mutuals, user, relationship]
  )

  const actorType = user?.pf_source?.actor_type
  const isCommunityActor = actorType === 'Group' || actorType === 'Organization'

  // Gate the filter the way the old ShowMenuBar() predicate did. Locked
  // accounts that the viewer doesn't follow don't expose their feed, so the
  // filter would be controlling a feed that renders the locked-account empty
  // state regardless. Mastodon profiles only have a single "Posts" tab — a
  // one-option filter would render an inert single icon, so suppress it.
  const canShowFeedFilter =
    !isCommunityActor &&
    !!user &&
    (isSelf || user.locked === false || !!relationship?.following) &&
    chrome.publicTabs.length > 1

  const statusesQuery = useInfiniteQuery({
    ...postQueries.profileStatuses(user?.id as string, chrome.statusesVariant(activeTab)),
    enabled: !!userId && !isCommunityActor,
  })

  const eventsQuery = useInfiniteQuery({
    ...postQueries.profileEvents(user?.id as string),
    enabled: !!userId && isCommunityActor,
  })

  // D1a — direct AP outbox fallback. Reached only for remote profiles
  // (`acct` containing `@`) when the home-instance feed is empty after the
  // first page settled. Rationale: the home server's flat
  // `whereProfileId` query has no on-demand federation pull, so a remote
  // profile reached via a single boosted post typically returns []. The
  // fallback fetches the actor's own outbox over AP and renders synthetic
  // entries through `FeedPost` (gated through `RemoteOutboxPostCell` for
  // resolve-on-tap). Skipped on community actors (they use the events tab),
  // on the grid tab (synthetic media often lacks dimensions Pixelfed
  // pre-computes for the grid), and on the events tab.
  //
  // D1b coexistence: when the home server advertises
  // `outbox_fetch_on_demand` (Halflight `FetchAccountOutboxOnDemandJob`),
  // the first cold-view dispatches a server-side outbox walk. Give the
  // server a couple of refetch cycles to materialise rows before falling
  // through to the direct-AP path; on non-Halflight servers (flag absent)
  // the fallback fires immediately as before.
  const homeServerCanBackfill = useFeature('outbox_fetch_on_demand')
  const isRemoteProfile = !!user?.acct && user.acct.includes('@')
  const homeFeedSettledEmpty =
    statusesQuery.isFetched &&
    !statusesQuery.isFetching &&
    !statusesQuery.isFetchingNextPage &&
    (statusesQuery.data?.pages?.flat().length ?? 0) === 0

  const MAX_HOME_BACKFILL_POLLS = 2
  const HOME_BACKFILL_POLL_DELAY_MS = 1500
  const [homeBackfillPolls, setHomeBackfillPolls] = useState(0)
  useEffect(() => {
    setHomeBackfillPolls(0)
  }, [userId, activeTab])
  useEffect(() => {
    if (!homeServerCanBackfill) return
    if (!isRemoteProfile) return
    if (isCommunityActor || showsGrid) return
    if (!homeFeedSettledEmpty) return
    if (homeBackfillPolls >= MAX_HOME_BACKFILL_POLLS) return
    const delay = HOME_BACKFILL_POLL_DELAY_MS * (homeBackfillPolls + 1)
    const timer = setTimeout(() => {
      setHomeBackfillPolls((n) => n + 1)
      statusesQuery.refetch()
    }, delay)
    return () => clearTimeout(timer)
  }, [
    homeServerCanBackfill,
    isRemoteProfile,
    isCommunityActor,
    showsGrid,
    homeFeedSettledEmpty,
    homeBackfillPolls,
  ])

  const homeBackfillExhausted =
    !homeServerCanBackfill || homeBackfillPolls >= MAX_HOME_BACKFILL_POLLS
  const shouldUseOutboxFallback =
    !isCommunityActor &&
    !showsGrid &&
    isRemoteProfile &&
    homeFeedSettledEmpty &&
    homeBackfillExhausted &&
    !!user
  const outboxQuery = useInfiniteQuery({
    ...postQueries.profileStatusesRemoteOutbox(
      user?.id as string,
      user as NonNullable<typeof user>
    ),
    enabled: !!userId && shouldUseOutboxFallback,
  })

  const usingOutbox = shouldUseOutboxFallback && outboxQuery.data !== undefined
  const outboxBlocked =
    shouldUseOutboxFallback &&
    outboxQuery.isError &&
    outboxQuery.error instanceof RemoteOutboxError

  const baseQuery = isCommunityActor ? eventsQuery : statusesQuery
  const { status, data: feed, isFetched, isFetching } = baseQuery
  const fetchNextPage = usingOutbox ? outboxQuery.fetchNextPage : baseQuery.fetchNextPage
  const hasNextPage = usingOutbox ? outboxQuery.hasNextPage : baseQuery.hasNextPage
  const isFetchingNextPage = usingOutbox
    ? outboxQuery.isFetchingNextPage
    : baseQuery.isFetchingNextPage

  const flattenedData = useMemo(() => {
    if (usingOutbox) {
      const pages = outboxQuery.data?.pages ?? []
      return pages.flatMap((p) => p.statuses)
    }
    if (!feed?.pages) return []
    return feed.pages.flat() as Status[]
  }, [feed?.pages, usingOutbox, outboxQuery.data?.pages])

  const handleTabChange = (value: string) => {
    if (!chrome.isPublicTab(value) || value === activeTab) return
    haptic.selection()
    // Phase 2.3: grid and microblog now have separate caches with their own
    // server-side filters, so each one can stay warm independently — no
    // invalidation needed on switch.
    setActiveTab(value)
  }

  useScreenMenu(
    user && !isSelf
      ? {
          intents: [
            ...(canShowFeedFilter
              ? ([
                  {
                    kind: 'filter' as const,
                    accessibilityLabel: 'Profile view',
                    value: activeTab,
                    options: chrome.publicTabs,
                    onChange: handleTabChange,
                    display: 'icons' as const,
                  },
                ] as const)
              : []),
            {
              kind: 'act',
              id: 'message',
              label: 'Message',
              icon: 'message-square',
              onPress: () => router.push(`/chats/conversation/${user.id}?id=${user.id}`),
            },
            // Relationship state machine. Exactly one branch fires (the API
            // guarantees blocking / requested / following are mutually
            // exclusive); each empty-array fall-through is a no-op spread.
            // Unblock / Cancel-Request / Follow are `weight: 'primary'`, so
            // `placeIntents` promotes them into the rightmost commit-pill
            // slot (see `intents.ts:203`). The `following` branch keeps its
            // existing `tertiary` weight — "Following" is a successful
            // steady-state, not a CTA, so visual hierarchy de-emphasizes it.
            ...(relationship?.blocking
              ? ([
                  {
                    kind: 'act' as const,
                    id: 'unblock',
                    label: 'Unblock',
                    icon: 'user-check' as const,
                    weight: 'primary' as const,
                    destructive: true,
                    confirm: `Unblock @${user.acct}? They won't be notified. You can block them later.`,
                    confirmTitle: 'Unblock account?',
                    confirmLabel: 'Unblock',
                    onPress: () => handleUnblock(),
                  },
                ] as const)
              : []),
            ...(relationship?.requested
              ? ([
                  {
                    kind: 'act' as const,
                    id: 'cancel-request',
                    label: 'Cancel follow request',
                    icon: 'user-minus' as const,
                    weight: 'primary' as const,
                    onPress: () => handleCancelFollowRequest(),
                  },
                ] as const)
              : []),
            ...(relationship?.following
              ? ([
                  {
                    kind: 'act' as const,
                    id: 'following',
                    label: 'Following',
                    icon: 'user-check' as const,
                    weight: 'tertiary' as const,
                    toggled: true,
                    destructive: true,
                    confirm: `Stop following @${user.acct}?`,
                    confirmTitle: 'Unfollow?',
                    confirmLabel: 'Unfollow',
                    onPress: () => handleUnfollow(),
                  },
                ] as const)
              : []),
            ...(relationship &&
            !relationship.following &&
            !relationship.requested &&
            !relationship.blocking
              ? ([
                  {
                    kind: 'act' as const,
                    id: 'follow',
                    label: user.locked ? 'Request follow' : 'Follow',
                    icon: 'user-plus' as const,
                    weight: 'primary' as const,
                    onPress: () => handleFollow(),
                  },
                ] as const)
              : []),
            {
              kind: 'menu',
              accessibilityLabel: 'Account options',
              items: [
                relationship?.muting
                  ? {
                      id: 'unmute',
                      label: 'Unmute',
                      icon: 'volume-2' as const,
                      confirm: `Unmute @${user.acct}?`,
                      confirmTitle: 'Unmute account?',
                      confirmLabel: 'Unmute',
                      onPress: () => handleUnmute(),
                    }
                  : {
                      id: 'mute',
                      label: 'Mute',
                      icon: 'volume-x' as const,
                      confirm:
                        "Mute this account? They won't be notified. You can unmute them later.",
                      confirmTitle: 'Mute account?',
                      confirmLabel: 'Mute',
                      destructive: true,
                      onPress: () => handleMute(),
                    },
                // When `relationship.blocking`, Unblock is the primary CTA
                // above — drop it from the overflow to avoid two slots
                // doing the same thing. When not blocking, Block stays in
                // the overflow as the affordance that initiates a block.
                ...(relationship?.blocking
                  ? []
                  : ([
                      {
                        id: 'block',
                        label: 'Block',
                        icon: 'user-x' as const,
                        confirm:
                          "Block this account? They won't be notified. You can unblock them later.",
                        confirmTitle: 'Block account?',
                        confirmLabel: 'Block',
                        destructive: true,
                        onPress: () => handleBlock(),
                      },
                    ] as const)),
                {
                  id: 'copyurl',
                  label: 'Copy profile URL',
                  icon: 'copy',
                  onPress: () => menuGotoLink('copyurl'),
                },
                // Mastodon-only: "Manage lists" pushes the Surface A
                // formSheet (per-account checkbox picker). Pixelfed has no
                // server-side list API, so we hide it on Pixelfed-active
                // accounts. The not-self guard is inherited from the
                // enclosing `user && !isSelf` block (line 549). We pass only
                // the scalar route params the sheet needs for its first paint
                // — emojis are sourced from `accountQueries.byId` inside the
                // sheet on first settle, no JSON serialization needed.
                ...(accountType === 'mastodon'
                  ? ([
                      {
                        id: 'manage-lists',
                        label: 'Manage lists',
                        icon: 'list' as const,
                        onPress: () =>
                          router.push({
                            pathname: '/settings/lists/for/[accountId]',
                            params: {
                              accountId: user.id,
                              displayName: user.display_name ?? '',
                              acct: user.acct ?? '',
                              avatar: user.avatar ?? '',
                            },
                          } as any),
                      },
                    ] as const)
                  : []),
                {
                  id: 'share',
                  label: 'Share',
                  icon: 'share-2',
                  onPress: () => menuGotoLink('share'),
                },
                {
                  id: 'report',
                  label: 'Report',
                  icon: 'alert-triangle',
                  destructive: true,
                  onPress: () => menuGotoLink('report'),
                },
              ],
            },
          ],
        }
      : { intents: [] }
  )

  if (userError) {
    return (
      <SafeAreaView
        edges={['top', 'bottom']}
        style={{ flex: 1, backgroundColor: theme.background.val }}
      >
        {byUsername ? (
          // Handle lookup failed — the account is genuinely unknown to this
          // server (or a remote handle that couldn't be resolved). Offer a
          // search, which can surface the account by webfinger.
          <EmptyState
            icon="user-x"
            title="Couldn't open this profile"
            subtitle={`@${byUsername} isn't on your server, or your server hasn't seen them yet.`}
            cta={{
              label: `Search for ${byUsername}`,
              onPress: () =>
                router.push({
                  pathname: '/search',
                  params: { initialQuery: byUsername },
                }),
            }}
          />
        ) : (
          // By-id lookup failed (network / server error). Searching a numeric
          // id is meaningless, so offer a retry instead.
          <EmptyState
            icon="alert-triangle"
            title="Couldn't open this profile"
            subtitle={userError.message}
            cta={{ label: 'Try again', onPress: () => refetchUser() }}
          />
        )}
      </SafeAreaView>
    )
  }

  if (status !== 'success' || (isFetching && !isFetchingNextPage)) {
    return (
      <SafeAreaView
        edges={['top', 'bottom']}
        style={{ flex: 1, backgroundColor: theme.background.val }}
      >
        <View style={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}>
          <ActivityIndicator color={theme.color.val} />
        </View>
      </SafeAreaView>
    )
  }

  return (
    <SafeAreaView
      edges={['top', 'bottom']}
      style={{ flex: 1, backgroundColor: theme.background.val }}
    >
      <FlashList
        key={isCommunityActor ? 'events' : activeTab}
        data={flattenedData}
        keyExtractor={(item: any) => item.id.toString()}
        ListHeaderComponent={RenderHeader}
        renderItem={
          isCommunityActor
            ? ({ item }: { item: any }) => <EventCard post={item} />
            : RenderItem
        }
        numColumns={isCommunityActor || !showsGrid ? 1 : 3}
        showsVerticalScrollIndicator={false}
        onEndReached={() => {
          if (!userError && !isFetching && !isFetchingNextPage && hasNextPage) {
            fetchNextPage()
          }
        }}
        onEndReachedThreshold={0.1}
        ListEmptyComponent={EmptyFeed}
        contentContainerStyle={{
          flexGrow: 1,
          backgroundColor: theme.background.val,
        }}
        ListFooterComponent={() =>
          !userError && isFetched && isFetchingNextPage ? (
            <View p="$5">
              <ActivityIndicator color={theme.color.val} />
            </View>
          ) : null
        }
      />
    </SafeAreaView>
  )
}
