import { Feather } from '@expo/vector-icons'
import type { FlashListRef } from '@shopify/flash-list'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Image } from 'expo-image'
import type { ErrorBoundaryProps } from 'expo-router'
import { useLocalSearchParams, useRouter } from 'expo-router'
import { useShareIntentContext } from 'expo-share-intent'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Pressable } from 'react-native'
import { EmptyState } from 'src/components/common/EmptyState'
import { ErrorState } from 'src/components/common/ErrorState'
import { ScreenScrollContainer } from 'src/components/layout'
import type { FilterOption } from 'src/components/menu'
import { useScreenMenu, useTabPressScrollToTop } from 'src/components/menu'
import StoriesRail from 'src/components/stories/StoriesRail'
import { TimelineList } from 'src/components/timeline/TimelineList'
import { PlaybackScope } from 'src/hooks/playback/PlaybackScope'
import { useHomeFeed } from 'src/hooks/useHomeFeed'
import type { LoginUserResponse, Status } from 'src/lib/api-types'
import { ICON } from 'src/lib/icons'
import { matchesLens, type PostLens } from 'src/lib/pagination'
import { listsQueries } from 'src/lib/queries/lists'
import { postQueries } from 'src/lib/queries/posts'
import { useActiveProfile, useAuth } from 'src/state/AuthProvider'
import {
  applyListOrder,
  appStorage,
  getHomeMastodonList,
  getListOrder,
  setHomeMastodonList,
  uiKeys,
} from 'src/state/storage'
import { useAccountScopedPref } from 'src/state/useAccountScopedPref'
import { openBrowserAsync } from 'src/utils'
import { Spinner, Text, useTheme, View, XStack } from 'tamagui'

const MOBILIZON_HOME_URL = 'https://joinmobilizon.org/'

// Home is the primary timeline. A client-side "lens" filter replaces the
// retired Updates tab. Three lenses: Photos, Events, Writing — text, quotes,
// and articles share a single surface since they read similarly and the
// separation felt like decision fatigue in use.
// Like/delete/share optimistic updates and paging live in useHomeFeed;
// rendering lives in TimelineList.

const HOME_LENSES: Array<FilterOption & { value: PostLens }> = [
  { value: 'photos', label: 'Media' },
  { value: 'events', label: 'Events' },
  { value: 'writing', label: 'Writing' },
]

const DEFAULT_LENS: PostLens = 'photos'
const VALID_LENSES = new Set<PostLens>(HOME_LENSES.map((o) => o.value))

// Map any previously-persisted lens value to the new three-lens taxonomy so
// users don't get silently bounced to Photos on upgrade.
const LEGACY_LENS_MAP: Record<string, PostLens> = {
  text: 'writing',
  quotes: 'writing',
  articles: 'writing',
  non_media: 'writing',
}

function readStoredLens(): PostLens {
  const v = appStorage.getString(uiKeys.homeLens)
  if (!v) return DEFAULT_LENS
  if (VALID_LENSES.has(v as PostLens)) return v as PostLens
  const migrated = LEGACY_LENS_MAP[v]
  if (migrated) {
    appStorage.set(uiKeys.homeLens, migrated)
    return migrated
  }
  return DEFAULT_LENS
}

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

export default function HomeScreenRoute() {
  return (
    <PlaybackScope name="home">
      <HomeScreen />
    </PlaybackScope>
  )
}

function HomeScreen() {
  const router = useRouter()
  const queryClient = useQueryClient()
  const listRef = useRef<FlashListRef<Status>>(null)
  const { hasShareIntent } = useShareIntentContext()
  const params = useLocalSearchParams()
  const [isPosting, setIsPosting] = useState(false)
  const theme = useTheme()
  const { profile: user } = useActiveProfile()
  const { user: account } = useAuth()
  // Mastodon-active home (Phase B §3 of the Mastodon follow-up plan): the
  // Pixelfed lens picker is replaced with the user's lists. The two states
  // live side-by-side rather than under a single union — the keys, MMKV
  // entries, and queryFns are different shapes, and a discriminated state
  // would only obscure that.
  const isMastodon = account?.type === 'mastodon'
  const accountId = account?.accountId ?? ''
  const [lens, setLensState] = useState<PostLens>(readStoredLens)
  // Re-hydrates on account switch and persists per-account; see
  // `useAccountScopedPref`. The Mastodon-only branching for the picker itself
  // stays in JSX below — `getHomeMastodonList` returns `'all'` for any
  // unset/non-Mastodon key, so re-hydrating on every account switch is a
  // safe no-op for Pixelfed accounts.
  const [selectedListId, setSelectedListId] = useAccountScopedPref(
    getHomeMastodonList,
    setHomeMastodonList
  )

  const setLens = useCallback((value: string) => {
    const next = value as PostLens
    setLensState(next)
    appStorage.set(uiKeys.homeLens, next)
  }, [])

  const { data: mastodonLists = [] } = useQuery({
    ...listsQueries.lists(),
    enabled: isMastodon,
  })

  const homeFeedListId = isMastodon && selectedListId !== 'all' ? selectedListId : null

  const {
    posts,
    lastPageUrls,
    isFetching,
    isFetchingNextPage,
    isRefetching,
    hasNextPage,
    status,
    isError,
    error,
    refetch,
    onEndReached,
    handlers,
  } = useHomeFeed({ listId: homeFeedListId })

  useEffect(() => {
    if (hasShareIntent) router.navigate('/camera')
  }, [hasShareIntent, router])

  useEffect(() => {
    if (params.ref30 === '1') {
      setIsPosting(true)
      const timer = setTimeout(() => {
        queryClient.invalidateQueries({ queryKey: postQueries.feed().queryKey })
        router.setParams({} as any)
        setIsPosting(false)
      }, 10000)
      return () => clearTimeout(timer)
    }
  }, [params.ref30, queryClient, router])

  useTabPressScrollToTop('home', listRef, refetch)

  useEffect(() => {
    if (!lastPageUrls.length) return
    // Defer warm-up until the JS thread has spare time so prefetch doesn't
    // compete with on-screen image decoding.
    const handle = requestIdleCallback(() => {
      Image.prefetch(lastPageUrls, 'memory-disk')
    })
    return () => cancelIdleCallback(handle)
  }, [lastPageUrls])

  const filterOptions = useMemo<FilterOption[]>(() => {
    if (!isMastodon) return HOME_LENSES
    // Apply the user's per-account preferred ordering from Settings → Lists.
    // `applyListOrder` keeps server-side adds at the bottom and drops removals.
    const ordered = applyListOrder(mastodonLists, getListOrder(accountId))
    return [
      { value: 'all', label: 'All' },
      ...ordered.map((l) => ({ value: l.id, label: l.title })),
    ]
  }, [isMastodon, mastodonLists, accountId])

  const filterValue = isMastodon ? selectedListId : lens
  const onFilterChange = isMastodon ? setSelectedListId : setLens

  useScreenMenu({
    intents: [
      {
        kind: 'filter',
        accessibilityLabel: isMastodon ? 'Pick list' : 'Filter timeline',
        value: filterValue,
        options: filterOptions,
        onChange: onFilterChange,
      },
    ],
  })

  // Mastodon: the list (or "All") IS the filter. Phase B §3.0 — every supported
  // post type renders in one chronological stream; no client-side `matchesLens`
  // slicing. Pixelfed: keep the existing client-side lens filter.
  const feedData = useMemo(
    () => (isMastodon ? posts : posts.filter((p) => matchesLens(p, lens))),
    [isMastodon, posts, lens]
  )

  const listHeader = useMemo(() => {
    return (
      <>
        {isMastodon ? null : <StoriesRail />}
        {isPosting ? (
          <View p="$5">
            <XStack gap="$3">
              <Spinner color={theme.color.val} />
              <Text fontSize="$5" allowFontScaling={false} color={theme.color.val}>
                Uploading new post, please wait...
              </Text>
            </XStack>
          </View>
        ) : null}
      </>
    )
  }, [isMastodon, isPosting, theme.color.val])

  const openMobilizon = useCallback(() => openBrowserAsync(MOBILIZON_HOME_URL), [])

  const emptyState = useMemo(() => {
    if (status !== 'success') return null
    // The Pixelfed-only Mobilizon nudge (Events lens empty state) is gated
    // off on Mastodon — there is no equivalent lens. Mastodon list-empty
    // states fall through to the generic copy below.
    if (!isMastodon && lens === 'events') {
      return (
        <EmptyState
          icon="calendar"
          title="No events yet"
          subtitle="Add one on Mobilizon to get started."
          cta={{ label: 'Open Mobilizon', onPress: openMobilizon }}
        />
      )
    }
    if (!isMastodon && lens === DEFAULT_LENS && posts.length === 0) {
      return {
        icon: 'inbox',
        title: 'Your feed is empty',
        subtitle: 'Try following some accounts to fill your timeline!',
      }
    }
    if (isMastodon && posts.length === 0) {
      // Selected list / home timeline returned nothing — generic empty.
      return {
        icon: 'inbox',
        title: 'Nothing here yet',
        subtitle:
          selectedListId === 'all'
            ? 'Try following some accounts to fill your timeline!'
            : 'This list has no recent posts.',
      }
    }
    return {
      icon: 'filter',
      title: 'Nothing matches this filter',
      subtitle: 'Try a different filter to see more of your timeline.',
    }
  }, [status, lens, posts.length, openMobilizon, isMastodon, selectedListId])

  const endOfFeed = useMemo(() => {
    if (isMastodon || lens !== 'events') return null
    return (
      <Pressable onPress={openMobilizon} hitSlop={8}>
        <XStack alignItems="center" justifyContent="center" gap="$2" py="$5" px="$4">
          <Feather
            name="external-link"
            size={ICON.sm}
            color={theme.interactiveActive.val}
          />
          <Text color={theme.interactiveActive.val} fontSize="$3" fontWeight="bold">
            Add a new event on Mobilizon
          </Text>
        </XStack>
      </Pressable>
    )
  }, [isMastodon, lens, openMobilizon, theme.interactiveActive.val])

  const getItemType = useCallback((item: Status) => {
    if (item.media_attachments?.length > 1) return 'carousel'
    if (item.media_attachments?.[0]?.type === 'video') return 'video'
    return 'image'
  }, [])

  if (isFetching && !isFetchingNextPage && !isRefetching && posts.length === 0) {
    return (
      <ScreenScrollContainer variant="scroll">
        <View flex={1} justifyContent="center" alignItems="center" py="$10">
          <Spinner color="$color" />
        </View>
      </ScreenScrollContainer>
    )
  }

  if (error || isError) {
    return (
      <ScreenScrollContainer variant="scroll">
        <ErrorState message="The service may be temporarily unavailable, please try again later." />
      </ScreenScrollContainer>
    )
  }

  return (
    <ScreenScrollContainer variant="animated-flashlist">
      {(scrollProps) => (
        <TimelineList
          listRef={listRef}
          data={feedData}
          user={user as LoginUserResponse}
          handlers={handlers}
          isRefetching={isRefetching}
          onRefresh={refetch}
          hasNextPage={hasNextPage}
          isFetchingNextPage={isFetchingNextPage}
          onEndReached={onEndReached}
          empty={emptyState}
          listHeader={listHeader}
          getItemType={getItemType}
          endOfFeed={endOfFeed}
          scrollInsets={scrollProps}
        />
      )}
    </ScreenScrollContainer>
  )
}
