// The Weave — Halflight tapestry screen.
//
// Live tail (current UTC minute, polled every 3 s while focused +
// foregrounded) at the top, infinite-scroll history below as 30-minute
// canonical pages. Halflight-gated at the screen level: a flag flip on the
// active account's instance redirects the route to the active tab.

import { FlashList } from '@shopify/flash-list'
import { type ErrorBoundaryProps, Redirect } from 'expo-router'
import { useMemo } from 'react'
import { ErrorState } from 'src/components/common/ErrorState'
import { LoadingView } from 'src/components/common/LoadingView'
import { ScreenScrollContainer } from 'src/components/layout'
import { TapestryRow } from 'src/components/tapestry/TapestryRow'
import { useFeature } from 'src/lib/features'
import { useTapestryPoll } from 'src/lib/tapestry/poll'
import { useTapestryQueue } from 'src/lib/tapestry/queue'
import {
  MAX_BACKFILL_PAGES,
  useEagerBackfill,
  useTapestryPages,
} from 'src/lib/tapestry/range'
import { useAuth } from 'src/state/AuthProvider'
import { Text, View, YStack } from 'tamagui'

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

type Row =
  | { kind: 'live'; bucket: string; letters: string; pending: string }
  | { kind: 'history'; bucket: string; letters: string }

const ROW_KEY = (item: Row) => `${item.kind}:${item.bucket}`

export default function TapestryScreen() {
  const enabled = useFeature('tapestry')
  // Going through `useAuth` (which mirrors `AccountStore` via
  // `useSyncExternalStore`) makes account switches re-render the screen
  // even when the new account also has the tapestry flag — `useFeature`
  // alone would only re-render on a flag *change*, not a host change.
  const { user } = useAuth()

  // Halflight gate. The redirect fires both for non-Halflight instances
  // (flag never advertised) and after an account switch to such an
  // instance.
  if (!enabled || !user) {
    return <Redirect href="/(auth)/(tabs)/home" />
  }
  const host = user.instance

  return (
    <ScreenScrollContainer variant="flashlist">
      {(scrollProps) => <TapestryList host={host} scrollProps={scrollProps} />}
    </ScreenScrollContainer>
  )
}

// Split out so the hooks below the early-return gate are unconditionally
// called once we're committed to rendering. Pulling the redirect up to the
// outer component keeps the hooks-rules simple.
function TapestryList({
  host,
  scrollProps,
}: {
  host: string
  scrollProps: {
    contentInsetAdjustmentBehavior: 'never'
    contentInset: { top: number; bottom: number }
    scrollIndicatorInsets: { top: number; bottom: number }
    contentContainerStyle: { paddingTop: number; paddingBottom: number }
  }
}) {
  const { liveBucket, liveLetters } = useTapestryPoll(host)
  const pending = useTapestryQueue(host)
  const query = useTapestryPages(host)
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    isSuccess,
    isError,
    error,
    refetch,
  } = query

  const items = useMemo<Row[]>(() => {
    const out: Row[] = []
    // The live row is suppressed when the current minute has produced
    // nothing locally and nothing on the server — an empty leading row
    // would read as a blank line above the history. The first non-empty
    // tick (poll response or local emit) reveals it.
    if (liveLetters.length > 0 || pending.length > 0) {
      out.push({
        kind: 'live',
        bucket: liveBucket,
        letters: liveLetters,
        pending,
      })
    }
    const seen = new Set<string>()
    seen.add(liveBucket)
    if (data) {
      for (const page of data.pages) {
        for (const row of page.rows) {
          if (seen.has(row.bucket)) continue
          seen.add(row.bucket)
          out.push({ kind: 'history', bucket: row.bucket, letters: row.letters })
        }
      }
    }
    return out
  }, [liveBucket, liveLetters, pending, data])

  // Walk back history pages eagerly on mount until the rendered list
  // overflows the viewport. Quiet instances return mostly-empty pages and
  // FlashList's `onEndReached` only fires once content scrolls past the
  // bottom — without this nudge a near-empty initial fetch never grew.
  useEagerBackfill(query, items.length)

  // Error early-out only when we have nothing to show. With cached pages
  // hydrated from the persisted query cache, transient errors don't blank
  // the screen — the FlashList keeps rendering whatever is cached.
  if (isError && items.length === 0) {
    return <ErrorState message={error?.message ?? 'Tapestry failed'} onRetry={refetch} />
  }

  // The eager loop is "done" when react-query has settled and either the
  // walker has nothing left to fetch or it's hit the 30-day retention cap.
  // Until then, an empty list means "still backfilling, show the spinner",
  // not "this instance is quiet" — so the QuietState copy only appears
  // after the 1440-page walk genuinely exhausts.
  const pagesFetched = data?.pages?.length ?? 0
  const eagerDone = isSuccess && (!hasNextPage || pagesFetched >= MAX_BACKFILL_PAGES)

  // FlashList v2 (@shopify/flash-list@2.3.1): rows are self-measured, so
  // `estimatedItemSize` is intentionally absent (removed in v2). MVCP is
  // enabled by default; the empty `maintainVisibleContentPosition={{}}` is
  // a no-op that makes the dependency explicit — if a future major flips
  // the default, this is a one-line breakage point instead of silent drift.
  // The v1-shaped `{ minIndexForVisible: 1 }` is *not* the v2 API.
  //
  // `flexGrow: 1` is merged into contentContainerStyle so ListEmptyComponent
  // (LoadingView / QuietState) fills the viewport instead of collapsing to
  // its intrinsic height.
  return (
    <FlashList
      data={items}
      keyExtractor={ROW_KEY}
      renderItem={renderItem}
      ItemSeparatorComponent={RowSpacer}
      maintainVisibleContentPosition={{}}
      onEndReached={() => {
        if (hasNextPage && !isFetchingNextPage) fetchNextPage()
      }}
      onEndReachedThreshold={0.5}
      ListEmptyComponent={eagerDone ? <QuietState /> : <LoadingView />}
      contentInsetAdjustmentBehavior={scrollProps.contentInsetAdjustmentBehavior}
      contentInset={scrollProps.contentInset}
      scrollIndicatorInsets={scrollProps.scrollIndicatorInsets}
      contentContainerStyle={{ ...scrollProps.contentContainerStyle, flexGrow: 1 }}
    />
  )
}

function QuietState() {
  return (
    <YStack flex={1} alignItems="center" justifyContent="center" padding="$8">
      <Text color="$colorTertiary" fontSize="$3" textAlign="center">
        The weave is quiet right now.
      </Text>
      <Text
        color="$colorTertiary"
        fontSize="$2"
        textAlign="center"
        marginTop="$2"
        opacity={0.7}
      >
        As people post on this instance, the weave fills in — scroll back to read what
        came before.
      </Text>
    </YStack>
  )
}

function renderItem({ item }: { item: Row }) {
  if (item.kind === 'live') {
    return (
      <TapestryRow
        bucket={item.bucket}
        letters={item.letters}
        pendingLetters={item.pending}
        isLive
      />
    )
  }
  return <TapestryRow bucket={item.bucket} letters={item.letters} />
}

function RowSpacer() {
  return <View height={2} />
}
