import { FlashList, type FlashListRef } from '@shopify/flash-list'
import { useInfiniteQuery, useSuspenseQuery } from '@tanstack/react-query'
import type { ErrorBoundaryProps } from 'expo-router'
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { RefreshControl } from 'react-native'
import { EmptyState } from 'src/components/common/EmptyState'
import { ErrorState } from 'src/components/common/ErrorState'
import { LoadingView } from 'src/components/common/LoadingView'
import { PostGridCell } from 'src/components/common/PostGridCell'
import {
  ScreenScrollContainer,
  useAccentRefreshControlProps,
} from 'src/components/layout'
import { useMenuScroll, useScreenMenu, useTabPressScrollToTop } from 'src/components/menu'
import { getLocalTimelinePosts } from 'src/lib/api'
import { useFeature } from 'src/lib/features'
import { haptic } from 'src/lib/haptics'
import { discoverQueries } from 'src/lib/queries/discover'
import { postQueries } from 'src/lib/queries/posts'
import { emit } from 'src/lib/tapestry/emit'

// Explore is the discovery surface. Local and Federated render the 3-col
// image grid; Local hits the instance's public timeline, Federated pages
// the remote network timeline via infinite scroll. When the server
// advertises `network_timeline_followed`, the Federated feed is narrowed
// server-side to remote authors someone on this instance follows — so it
// reads as "what our network is doing out there" instead of the raw
// firehose.
type ExploreMode = 'local' | 'federated'

const NUM_COLUMNS = 3

const isValidPost = (post: any) => {
  if (!post || typeof post !== 'object') return false
  if (!post.id) return false
  if (!Array.isArray(post.media_attachments) || post.media_attachments.length === 0)
    return false
  if (post.sensitive === true) return false
  if (
    typeof post.content_text === 'string' &&
    post.content_text.toLowerCase().includes('#nsfw')
  )
    return false

  return true
}

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

export default function DiscoverScreen() {
  return (
    <Suspense
      fallback={
        <ScreenScrollContainer variant="scroll">
          <LoadingView />
        </ScreenScrollContainer>
      }
    >
      <ExploreContent />
    </Suspense>
  )
}

function ExploreContent() {
  const [refreshing, setRefreshing] = useState(false)
  const [mode, setMode] = useState<ExploreMode>('local')
  const gridRef = useRef<FlashListRef<any>>(null)
  const hasFollowed = useFeature('network_timeline_followed')
  const refreshProps = useAccentRefreshControlProps()

  const { data: localPosts, refetch: refetchLocalPosts } = useSuspenseQuery({
    ...discoverQueries.localTimeline(),
    queryFn: () =>
      getLocalTimelinePosts().then((result) => (Array.isArray(result) ? result : [])),
    retry: 3,
    retryDelay: 1000,
  })

  // On Halflight-backed instances the server narrows the federated feed to
  // remote accounts local users follow; stock Pixelfed ignores the flag.
  const federatedFilter = hasFollowed ? 'followed' : 'all'

  const {
    data: federatedData,
    fetchNextPage: fetchFederatedNextPage,
    hasNextPage: federatedHasNextPage,
    isFetchingNextPage: federatedIsFetchingNextPage,
    isRefetching: federatedIsRefetching,
    refetch: refetchFederated,
  } = useInfiniteQuery({
    ...postQueries.networkFeed(federatedFilter),
    enabled: mode === 'federated',
  })

  const federatedPosts = useMemo(
    () => federatedData?.pages.flatMap((page) => page?.data ?? []) ?? [],
    [federatedData]
  )

  const gridPosts = useMemo(() => {
    const source =
      mode === 'federated' ? federatedPosts : Array.isArray(localPosts) ? localPosts : []

    const uniquePosts = new Map()
    for (const post of source) {
      if (isValidPost(post) && !uniquePosts.has(post.id)) {
        uniquePosts.set(post.id, post)
      }
    }
    return Array.from(uniquePosts.values())
  }, [mode, localPosts, federatedPosts])

  // Tapestry telemetry — record an explore-visit on first mount. Re-tap of
  // the focused tab also counts as a visit and is wired through the
  // `useTabPressScrollToTop` callback below so we don't duplicate the
  // scroll-reset / refetch path here.
  useEffect(() => {
    emit('e')
  }, [])

  // Re-tap of the focused Explore tab refetches the active mode's feed,
  // scrolls to top, and records a tapestry visit.
  const refetchActive = useCallback(() => {
    emit('e')
    refetchLocalPosts().catch(() => {})
    if (mode === 'federated') refetchFederated().catch(() => {})
  }, [refetchLocalPosts, refetchFederated, mode])
  useTabPressScrollToTop('explore', gridRef, refetchActive)

  const onRefresh = useCallback(async () => {
    setRefreshing(true)
    try {
      if (mode === 'federated') {
        await refetchFederated()
      } else {
        await refetchLocalPosts()
      }
    } finally {
      setRefreshing(false)
    }
  }, [mode, refetchFederated, refetchLocalPosts])

  const RenderGridPostItem = useCallback(({ item }: any) => {
    if (!isValidPost(item)) return null
    return <PostGridCell post={item} numColumns={NUM_COLUMNS} />
  }, [])

  const gridKeyExtractor = useCallback((item: any, index: any) => {
    try {
      return item?.id?.toString() || `post-${index}`
    } catch {
      return `post-${index}-${Date.now()}`
    }
  }, [])

  const MODE_TABS: Array<{ value: ExploreMode; label: string }> = useMemo(
    () => [
      { value: 'local', label: 'Local' },
      { value: 'federated', label: 'Federated' },
    ],
    []
  )

  useScreenMenu({
    intents: [
      {
        kind: 'filter',
        accessibilityLabel: 'Timeline source',
        value: mode,
        options: MODE_TABS,
        onChange: (v) => {
          haptic.selection()
          setMode(v as ExploreMode)
        },
      },
      {
        kind: 'search',
        placeholder: 'Search accounts, hashtags, posts',
        mode: 'route',
        routeTo: '/search',
      },
    ],
  })

  const onScroll = useMenuScroll(true)

  const onEndReached = useCallback(() => {
    if (mode === 'federated' && federatedHasNextPage && !federatedIsFetchingNextPage) {
      fetchFederatedNextPage()
    }
  }, [mode, federatedHasNextPage, federatedIsFetchingNextPage, fetchFederatedNextPage])

  const isRefreshing = refreshing || (mode === 'federated' && federatedIsRefetching)

  const emptyTitle =
    mode === 'federated'
      ? hasFollowed
        ? 'No recent posts from accounts people here follow'
        : 'No recent posts from the federated network'
      : 'No recent posts from this instance'

  if (!Array.isArray(gridPosts) || gridPosts.length === 0) {
    return (
      <ScreenScrollContainer variant="scroll">
        <EmptyState title={emptyTitle} />
      </ScreenScrollContainer>
    )
  }

  return (
    <ScreenScrollContainer variant="flashlist">
      {(scrollProps) => (
        <FlashList
          ref={gridRef}
          data={gridPosts}
          renderItem={RenderGridPostItem}
          keyExtractor={gridKeyExtractor}
          numColumns={NUM_COLUMNS}
          showsVerticalScrollIndicator={false}
          onScroll={onScroll}
          onEndReached={onEndReached}
          onEndReachedThreshold={0.5}
          refreshControl={
            <RefreshControl
              refreshing={isRefreshing}
              onRefresh={onRefresh}
              progressViewOffset={scrollProps.topInset}
              {...refreshProps}
            />
          }
          contentInsetAdjustmentBehavior={scrollProps.contentInsetAdjustmentBehavior}
          contentInset={scrollProps.contentInset}
          scrollIndicatorInsets={scrollProps.scrollIndicatorInsets}
          contentContainerStyle={scrollProps.contentContainerStyle}
        />
      )}
    </ScreenScrollContainer>
  )
}
