import { Feather } from '@expo/vector-icons'
import { usePreventRemove } from '@react-navigation/native'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { router, useLocalSearchParams, useNavigation } from 'expo-router'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Alert, Pressable } from 'react-native'
import EmojiText from 'src/components/common/EmojiText'
import { ErrorState } from 'src/components/common/ErrorState'
import { LoadingView } from 'src/components/common/LoadingView'
import { UserRow } from 'src/components/common/UserRow'
import { ScreenScrollContainer } from 'src/components/layout'
import { useScreenMenu } from 'src/components/menu'
import { BodyText, FootnoteText, SectionHeader } from 'src/components/ui'
import { useAddListMembersMutation } from 'src/hooks/mutations/useAddListMembersMutation'
import { useRemoveListMembersMutation } from 'src/hooks/mutations/useRemoveListMembersMutation'
import type { MastodonList } from 'src/lib/api/mastodon'
import type { Account } from 'src/lib/api-types'
import { haptic } from 'src/lib/haptics'
import { accountQueries } from 'src/lib/queries/accounts'
import { queryKeys } from 'src/lib/queries/keys'
import { listsQueries } from 'src/lib/queries/lists'
import { useAuth } from 'src/state/AuthProvider'
import {
  Separator,
  Text,
  useTheme,
  useToastController,
  View,
  XStack,
  YStack,
} from 'tamagui'

// Surface A — Per-account checkbox picker.
//
// Presented as a `formSheet` route (registered in `src/app/_layout.tsx`) over
// the profile screen. Path: `/settings/lists/for/<accountId>`. The literal
// `for/` segment prevents collision with Surface B's `[id].tsx` sibling.
//
// State model: `original` is captured once on first settle into a ref;
// `current` mutates with each tap. Save dispatches the diff as one
// `Promise.all` of per-list mutations and pops the sheet.
export default function ManageListsForAccountScreen() {
  const theme = useTheme()
  const toast = useToastController()
  const qc = useQueryClient()
  const navigation = useNavigation()
  const { accountId, displayName, acct, avatar } = useLocalSearchParams<{
    accountId: string
    displayName?: string
    acct?: string
    avatar?: string
  }>()
  const { user } = useAuth()
  const isMastodon = user?.type === 'mastodon'

  const addMembers = useAddListMembersMutation()
  const removeMembers = useRemoveListMembersMutation()

  // The subject anchor renders from route-param scalars on the first frame to
  // avoid a blank avatar+name while the network settles, then upgrades to the
  // canonical account once `accountQueries.byId` resolves (which carries the
  // `emojis[]` array needed for custom-emoji shortcodes in the display name).
  // The query is always enabled so the emoji upgrade lands even when the
  // entry point happens to have pre-warmed every other field.
  const { data: account } = useQuery({
    ...accountQueries.byId(accountId as string),
    enabled: isMastodon && !!accountId,
  })
  const subject = useMemo<Account>(() => {
    if (account) return account as Account
    return {
      id: accountId as string,
      acct: acct ?? '',
      username: acct ?? '',
      display_name: displayName ?? '',
      avatar: avatar ?? '',
    } as unknown as Account
  }, [account, accountId, acct, avatar, displayName])

  // Both fetches in parallel — neither blocks the other; cache settles into
  // both for use on subsequent opens.
  const {
    data: lists,
    isPending: listsPending,
    error: listsError,
    refetch: refetchLists,
  } = useQuery({ ...listsQueries.lists(), enabled: isMastodon })

  const {
    data: forAccount,
    isPending: forAccountPending,
    error: forAccountError,
    refetch: refetchForAccount,
  } = useQuery({
    ...listsQueries.forAccount(accountId as string),
    enabled: isMastodon && !!accountId,
  })

  const originalRef = useRef<Set<string> | null>(null)
  const [current, setCurrent] = useState<Set<string>>(new Set())
  // Capture `original` once on first settle so optimistic mid-save updates
  // can't shift the baseline (per proposal §3.1 "Implementation note").
  useEffect(() => {
    if (originalRef.current === null && Array.isArray(forAccount)) {
      const set = new Set(forAccount.map((l) => l.id))
      originalRef.current = set
      setCurrent(new Set(set))
    }
  }, [forAccount])

  const added = useMemo(() => {
    const orig = originalRef.current
    if (!orig) return []
    return [...current].filter((id) => !orig.has(id))
  }, [current])
  const removed = useMemo(() => {
    const orig = originalRef.current
    if (!orig) return []
    return [...orig].filter((id) => !current.has(id))
  }, [current])
  const diffCount = added.length + removed.length

  // Lock the group order on mount — re-sorting on toggle would pull the row
  // out from under the user's finger.
  const initialMemberOrderRef = useRef<string[] | null>(null)
  useEffect(() => {
    if (initialMemberOrderRef.current === null && Array.isArray(forAccount)) {
      initialMemberOrderRef.current = forAccount.map((l) => l.id)
    }
  }, [forAccount])

  const { memberOf, others } = useMemo(() => {
    if (!Array.isArray(lists)) return { memberOf: [], others: [] }
    const initialMember = new Set(initialMemberOrderRef.current ?? [])
    const memberOrder = new Map(
      (initialMemberOrderRef.current ?? []).map((id, i) => [id, i] as const)
    )
    const memberOf = lists
      .filter((l) => initialMember.has(l.id))
      .sort((a, b) => (memberOrder.get(a.id) ?? 0) - (memberOrder.get(b.id) ?? 0))
    const others = lists
      .filter((l) => !initialMember.has(l.id))
      .sort((a, b) => a.title.localeCompare(b.title))
    return { memberOf, others }
  }, [lists])

  const [saving, setSaving] = useState(false)

  const titleForList = (listId: string) =>
    Array.isArray(lists) ? (lists.find((l) => l.id === listId)?.title ?? listId) : listId

  // Fire add/remove per-list in parallel; collect rejections so the toast can
  // name *which* list failed instead of a generic "some changes did not save".
  // Per-key rollback is already correct inside each mutation's snapshot — the
  // failed list's optimistic cache restore happens before this `allSettled`
  // resolves, so the UI rolls back without any extra wiring here.
  const onSave = async () => {
    if (saving || diffCount === 0) return
    haptic.selection()
    setSaving(true)
    try {
      const targetIds = [...added, ...removed]
      const calls = [
        ...added.map((listId) =>
          addMembers.mutateAsync({
            listId,
            accountIds: [accountId as string],
            accounts: [subject],
          })
        ),
        ...removed.map((listId) =>
          removeMembers.mutateAsync({
            listId,
            accountIds: [accountId as string],
          })
        ),
      ]
      const results = await Promise.allSettled(calls)
      const failedIds = results
        .map((r, i) => (r.status === 'rejected' ? targetIds[i] : null))
        .filter((id): id is string => id !== null)
      if (failedIds.length === 0) {
        // Reset the baseline so usePreventRemove doesn't fire on dismiss.
        originalRef.current = new Set(current)
        // Foreign mid-session changes lose to last-write-wins; refetch on
        // dismount so the next open is in sync (§7.14).
        qc.invalidateQueries({
          queryKey: queryKeys.lists.forAccount(accountId as string),
        })
        router.back()
      } else {
        const titles = failedIds.map(titleForList)
        const message =
          titles.length === 1
            ? `Could not save “${titles[0]}”.`
            : titles.length <= 3
              ? `Could not save: ${titles.map((t) => `“${t}”`).join(', ')}.`
              : `${titles.length} lists did not save.`
        toast.show(message, { preset: 'error', duration: 3500 })
      }
    } finally {
      setSaving(false)
    }
  }

  usePreventRemove(diffCount > 0 && !saving, ({ data }) => {
    Alert.alert('Discard changes?', 'You have unsaved list changes.', [
      { text: 'Keep editing', style: 'cancel' },
      {
        text: 'Discard',
        style: 'destructive',
        onPress: () => navigation.dispatch(data.action),
      },
    ])
  })

  useScreenMenu({
    title: 'Add to lists',
    intents: [
      { kind: 'back', dismissMode: 'close' },
      {
        kind: 'commit',
        id: 'save-memberships',
        icon: 'check',
        label: diffCount > 0 ? `Save (${diffCount})` : 'Save',
        onPress: onSave,
        state: saving ? 'busy' : diffCount > 0 ? 'idle' : 'disabled',
      },
    ],
  })

  if (!isMastodon) {
    return (
      <ScreenScrollContainer variant="scroll" header="Add to lists">
        <YStack px="$5" pt="$4" gap="$2">
          <BodyText tone="secondary">
            Lists are a Mastodon-only feature. Switch to a Mastodon account.
          </BodyText>
        </YStack>
      </ScreenScrollContainer>
    )
  }

  if (listsPending || forAccountPending) return <LoadingView />
  if (listsError) return <ErrorState onRetry={refetchLists} />
  if (forAccountError) return <ErrorState onRetry={refetchForAccount} />

  const toggle = (id: string) => {
    haptic.selection()
    setCurrent((prev) => {
      const next = new Set(prev)
      if (next.has(id)) next.delete(id)
      else next.add(id)
      return next
    })
  }

  const hasLists = (lists?.length ?? 0) > 0

  return (
    <ScreenScrollContainer variant="scroll" header="Add to lists">
      <YStack pt="$3">
        <View bg="$backgroundSecondary" mx="$5" borderRadius="$3" overflow="hidden">
          <UserRow account={subject as any} />
        </View>
      </YStack>

      {!hasLists ? (
        <EmptyLists onCreate={() => router.push('/settings/lists/new' as any)} />
      ) : (
        <>
          {memberOf.length > 0 ? (
            <>
              <SectionHeader>{`Member of (${memberOf.length})`}</SectionHeader>
              <YStack px="$5">
                {memberOf.map((list, i) => (
                  <View key={list.id}>
                    {i > 0 ? <Separator borderColor="$borderColor" /> : null}
                    <ListCheckboxRow
                      list={list}
                      checked={current.has(list.id)}
                      onToggle={() => toggle(list.id)}
                      theme={theme}
                    />
                  </View>
                ))}
              </YStack>
            </>
          ) : null}

          {others.length > 0 ? (
            <>
              <SectionHeader>
                {memberOf.length > 0 ? 'All lists' : 'Your lists'}
              </SectionHeader>
              <YStack px="$5">
                {others.map((list, i) => (
                  <View key={list.id}>
                    {i > 0 ? <Separator borderColor="$borderColor" /> : null}
                    <ListCheckboxRow
                      list={list}
                      checked={current.has(list.id)}
                      onToggle={() => toggle(list.id)}
                      theme={theme}
                    />
                  </View>
                ))}
              </YStack>
            </>
          ) : null}
        </>
      )}
    </ScreenScrollContainer>
  )
}

function EmptyLists({ onCreate }: { onCreate: () => void }) {
  const theme = useTheme()
  return (
    <YStack px="$5" pt="$8" gap="$3" alignItems="center">
      <View p="$5" borderWidth={2} borderColor={theme.borderColor.val} borderRadius={100}>
        <Feather name="list" size={28} color={theme.colorTertiary.val as string} />
      </View>
      <BodyText tone="secondary">No lists yet</BodyText>
      <Pressable
        onPress={onCreate}
        style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
      >
        <XStack
          bg="$backgroundSecondary"
          px="$4"
          py="$3"
          borderRadius="$3"
          alignItems="center"
          gap="$2"
        >
          <Feather name="plus" size={16} color={theme.color.val} />
          <Text color="$color" fontWeight="600">
            Create your first list
          </Text>
        </XStack>
      </Pressable>
    </YStack>
  )
}

function ListCheckboxRow({
  list,
  checked,
  onToggle,
  theme,
}: {
  list: MastodonList
  checked: boolean
  onToggle: () => void
  theme: ReturnType<typeof useTheme>
}) {
  return (
    <Pressable
      onPress={onToggle}
      accessibilityRole="checkbox"
      accessibilityState={{ checked }}
      accessibilityLabel={list.title}
      style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
    >
      <XStack bg="$backgroundSecondary" px="$4" py="$3" alignItems="center" gap="$3">
        <Feather name="list" size={18} color={theme.color.val} />
        <View flex={1}>
          <EmojiText
            text={list.title}
            variant="body"
            fontWeight="600"
            numberOfLines={1}
          />
          {list.exclusive ? <FootnoteText tone="tertiary">Exclusive</FootnoteText> : null}
        </View>
        <Feather
          name={checked ? 'check-circle' : 'circle'}
          size={22}
          color={checked ? theme.interactiveActive.val : theme.colorTertiary.val}
        />
      </XStack>
    </Pressable>
  )
}
