import ProfileHeader from '@components/profile/ProfileHeader'
import { useIsFocused } from '@react-navigation/native'
import { FlashList, type FlashListRef } from '@shopify/flash-list'
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'
import { type ErrorBoundaryProps, useLocalSearchParams, useRouter } from 'expo-router'
import { useCallback, useMemo, useRef, useState } from 'react'
import { Share } from 'react-native'
import Animated from 'react-native-reanimated'
import { EmptyState } from 'src/components/common/EmptyState'
import { ErrorState } from 'src/components/common/ErrorState'
import { ListFooter } from 'src/components/common/ListFooter'
import { LoadingView } from 'src/components/common/LoadingView'
import { PostGridCell } from 'src/components/common/PostGridCell'
import { ScreenScrollContainer } from 'src/components/layout'
import {
  useMenuAnimatedScroll,
  useScreenMenu,
  useTabPressScrollToTop,
} from 'src/components/menu'
import FeedPost from 'src/components/post/FeedPost'
import { useShareMutation } from 'src/hooks/mutations/useShareMutation'
import { PlaybackScope } from 'src/hooks/playback/PlaybackScope'
import { haptic } from 'src/lib/haptics'
import { type ProfileTab, profileChrome } from 'src/lib/protocol/chrome'
import { postQueries } from 'src/lib/queries/posts'
import { useActiveProfile, useAuth } from 'src/state/AuthProvider'
import { useToastController } from 'tamagui'

// Animated wrapper around FlashList so our scroll handler runs on the UI
// thread (matching the home timeline pattern). Wrapping once at module
// scope keeps the ref/prop typings identical to the underlying FlashList.
const AnimatedFlashList = Animated.createAnimatedComponent(
  FlashList as any
) as unknown as typeof FlashList

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

const EMPTY_COPY: Record<ProfileTab, { icon: string; title: string; subtitle?: string }> =
  {
    posts: { icon: 'camera', title: 'No Posts Yet' },
    microblog: { icon: 'list', title: 'No Posts Yet' },
    likes: {
      icon: 'heart',
      title: 'No Liked Posts',
      subtitle: 'Posts you like will appear here',
    },
    bookmarks: {
      icon: 'bookmark',
      title: 'No Bookmarks',
      subtitle: 'Posts you bookmark will appear here',
    },
  }

// Hard wall between accounts. Phase 2.1 made `(activeAccountId, userCache)`
// an atomic snapshot, but the tab screen itself never unmounts across a
// switch — internal FlashList scroll position, recycler caches, and any
// closure-captured ids survive into the new account's render. Re-keying
// the inner component on `activeAccountId` forces a clean unmount/remount,
// matching the user requirement that an account switch be a full reset.
export default function ProfileScreen() {
  const activeAccountId = useAuth().activeAccountId
  return (
    <PlaybackScope name="profile:self">
      <ProfileScreenInner key={activeAccountId ?? '__none__'} />
    </PlaybackScope>
  )
}

function ProfileScreenInner() {
  const { profile: user, summary, isFetching } = useActiveProfile()
  const isFocused = useIsFocused()
  const router = useRouter()
  const toast = useToastController()
  const queryClient = useQueryClient()
  const accountType = useAuth().user?.type ?? 'pixelfed'
  const chrome = useMemo(() => profileChrome(accountType), [accountType])

  const { tab: initialTab } = useLocalSearchParams<{ tab?: string }>()
  const [activeTab, setActiveTab] = useState<ProfileTab>(
    chrome.isSelfTab(initialTab) ? initialTab : 'posts'
  )

  // Prefer the registry's userId (single source of truth) over `user?.id` so
  // a corrupted/stale cached blob can't leak into infinite-query keys.
  const userId = summary?.userId ?? user?.id

  const onShare = async () => {
    try {
      await Share.share({ message: (user as any)?.url })
    } catch (error: any) {
      toast.show(error?.message ?? 'Unable to share', { preset: 'error' })
    }
  }

  const handleTabChange = (value: string) => {
    if (!chrome.isSelfTab(value) || value === activeTab) return
    haptic.selection()
    if (userId) {
      // Phase 2.3: grid and microblog now have separate caches with their
      // own server-side filters (`only_media=true` vs unfiltered), so each
      // can stay warm independently — only invalidate when switching to a
      // tab whose data we want to refresh.
      const keyByTab: Record<ProfileTab, readonly unknown[] | null> = {
        posts: null,
        microblog: null,
        likes: postQueries.selfLikes().queryKey,
        bookmarks: postQueries.selfBookmarks().queryKey,
      }
      const nextKey = keyByTab[value]
      if (nextKey) queryClient.invalidateQueries({ queryKey: nextKey })
    }
    setActiveTab(value)
  }

  useScreenMenu({
    intents: [
      {
        kind: 'filter',
        accessibilityLabel: 'Profile view',
        value: activeTab,
        options: chrome.selfTabs,
        onChange: handleTabChange,
        display: 'icons',
      },
      {
        kind: 'act',
        id: 'settings',
        label: 'Settings',
        icon: 'settings',
        onPress: () => router.push('/settings'),
      },
      {
        kind: 'act',
        id: 'share',
        label: 'Share profile',
        icon: 'share',
        onPress: onShare,
      },
    ],
  })

  const showStatuses = activeTab === 'posts' || activeTab === 'microblog'
  const statusesVariant = chrome.statusesVariant(activeTab)

  const statusesQuery = useInfiniteQuery({
    ...postQueries.profileStatuses(userId as string, statusesVariant),
    enabled: !!userId && showStatuses,
    subscribed: isFocused && showStatuses,
  })

  const likesQuery = useInfiniteQuery({
    ...postQueries.selfLikes(),
    enabled: activeTab === 'likes',
    subscribed: isFocused && activeTab === 'likes',
  })

  const bookmarksQuery = useInfiniteQuery({
    ...postQueries.selfBookmarks(),
    enabled: activeTab === 'bookmarks',
    subscribed: isFocused && activeTab === 'bookmarks',
  })

  const active = showStatuses
    ? statusesQuery
    : activeTab === 'likes'
      ? likesQuery
      : bookmarksQuery

  // profileStatuses pages are Status[]. selfLikes / selfBookmarks pages are
  // PaginatedStatus ({ data: Status[] }) — the pagination helpers differ.
  const flattenedData = useMemo<any[]>(() => {
    const pages = active.data?.pages
    if (!pages) return []
    if (activeTab === 'likes' || activeTab === 'bookmarks') {
      return pages.flatMap((p: any) => p?.data ?? [])
    }
    return (pages as any[]).flat()
  }, [activeTab, active.data?.pages])

  const onScroll = useMenuAnimatedScroll(true)

  // `key={activeTab}` on the AnimatedFlashList below forces a remount on tab
  // switch — the ref re-binds to the new instance on each remount, and the
  // hook's listener calls `.current?.scrollToOffset` lazily so a stale
  // pointer between the unmount/remount frames is harmless. Tab re-tap
  // refetches whichever segment query is currently active; an `activeRef`
  // captures the latest query without re-subscribing on every fetch state
  // change.
  const listRef = useRef<FlashListRef<any> | null>(null)
  const activeRef = useRef(active)
  activeRef.current = active
  const refetchActive = useCallback(() => {
    activeRef.current.refetch()
  }, [])
  useTabPressScrollToTop('profile', listRef, refetchActive)

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

  const showsGrid = chrome.isGridTab(activeTab)
  const renderItem = useCallback(
    ({ item }: { item: any }) => {
      if (showsGrid) {
        return <PostGridCell post={item} />
      }
      return (
        <FeedPost
          post={item}
          user={user as any}
          handleLikeProfileId={activeTab === 'likes'}
          isLikeFeed={activeTab === 'likes'}
          onOpenComments={() => onOpenComments(item.id)}
          onDeletePost={() => {}}
          onShare={() => onPostShare(item.id, item.reblogged === true)}
          {...(activeTab === 'likes' ? ({ likedAt: item?.liked_at } as any) : {})}
        />
      )
    },
    [showsGrid, activeTab, user, onOpenComments, onPostShare]
  )

  const emptyState = () => {
    if (active.isFetching && !active.isFetchingNextPage) return <LoadingView />
    const copy = EMPTY_COPY[activeTab]
    return <EmptyState icon={copy.icon} title={copy.title} subtitle={copy.subtitle} />
  }

  if (isFetching && !userId) {
    return (
      <ScreenScrollContainer variant="scroll">
        <LoadingView />
      </ScreenScrollContainer>
    )
  }

  return (
    <ScreenScrollContainer variant="animated-flashlist">
      {(scrollProps) => (
        <AnimatedFlashList
          ref={listRef}
          key={activeTab}
          data={flattenedData}
          keyExtractor={(item: any) => item?.id?.toString() ?? ''}
          ListHeaderComponent={<ProfileHeader profile={user} mutuals={[]} />}
          renderItem={renderItem}
          numColumns={showsGrid ? 3 : 1}
          showsVerticalScrollIndicator={false}
          onEndReached={() => {
            if (!active.isFetching && active.hasNextPage) active.fetchNextPage()
          }}
          ListEmptyComponent={emptyState}
          onEndReachedThreshold={0.5}
          ListFooterComponent={<ListFooter loading={active.isFetchingNextPage} />}
          onScroll={onScroll}
          contentInsetAdjustmentBehavior={scrollProps.contentInsetAdjustmentBehavior}
          contentInset={scrollProps.contentInset}
          scrollIndicatorInsets={scrollProps.scrollIndicatorInsets}
          contentContainerStyle={scrollProps.contentContainerStyle}
        />
      )}
    </ScreenScrollContainer>
  )
}
