// The Weave — Halflight tapestry screen.
//
// Live tail (current UTC minute, polled every 3 s while focused +
// foregrounded) at the top, scroll-back history below — all rendered as
// one continuous monospace wall whose texture comes from per-class
// color contrast rather than whitespace. Per-minute breaks chopped
// busy minutes into mid-line breaks and stretched quiet minutes into
// 1–3-character lines that read as broken; the wall lets the weave
// fill the column the way the design intended. Halflight-gated at
// the screen level: a flag flip on the active account's instance
// redirects the route to the active tab.

import { type ErrorBoundaryProps, Redirect } from 'expo-router'
import { useMemo } from 'react'
import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'
import { ErrorState } from 'src/components/common/ErrorState'
import { LoadingView } from 'src/components/common/LoadingView'
import { type ScreenScrollChildProps, ScreenScrollContainer } from 'src/components/layout'
import { WeaveText } from 'src/components/tapestry/WeaveText'
import { useFeature } from 'src/lib/features'
import { useTapestryPoll } from 'src/lib/tapestry/poll'
import { useTapestryQueue } from 'src/lib/tapestry/queue'
import {
  MIN_LENGTH_FOR_VIEWPORT,
  useEagerBackfill,
  useTapestryGapFill,
  useTapestryPages,
} from 'src/lib/tapestry/range'
import { useAuth } from 'src/state/AuthProvider'
import { ScrollView, Text, YStack } from 'tamagui'

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

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) => <TapestryWall host={host} scrollProps={scrollProps} />}
    </ScreenScrollContainer>
  )
}

function TapestryWall({
  host,
  scrollProps,
}: {
  host: string
  scrollProps: ScreenScrollChildProps
}) {
  const { liveBucket, liveLetters } = useTapestryPoll(host)
  const pending = useTapestryQueue(host)
  const query = useTapestryPages(host)
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    isError,
    error,
    refetch,
  } = query

  // Concatenate newest-first. `seen` dedupes the live minute against any
  // history-page row that happens to cover it (the live tail is the
  // authority for the current minute).
  const weave = useMemo(() => {
    let out = liveLetters
    const seen = new Set<string>([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 += row.letters
        }
      }
    }
    return out
  }, [liveBucket, liveLetters, data])

  // Background eager backfill until the wall is long enough to scroll.
  // Without this a quiet instance never overflows the viewport and the
  // user can't scroll to trigger onScroll-driven pagination.
  useEagerBackfill(query, weave.length, MIN_LENGTH_FOR_VIEWPORT)

  // Cold-launch gap-fill: on rehydration from the persisted query cache
  // the newest page can be hours behind the wall clock. One bulk
  // `/api/v1/tapestry/since/{bucket}` request closes the gap; if the
  // server hasn't deployed it yet we fall back to walking pages
  // forward via `fetchPreviousPage` (capped at 48 h).
  useTapestryGapFill(query, host)

  // Loading-state nuance: previously the screen waited for `eagerDone`
  // (entire 30-day walk-back complete OR hasNextPage === false) before
  // swapping the spinner for content; on a quiet instance that meant
  // ~2.4 minutes of "stuck loading". Switch the moment the first page
  // settles — eager walker keeps running silently underneath.
  const firstPageSettled = (data?.pages?.length ?? 0) >= 1
  const totalLength = weave.length + pending.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 wall keeps rendering whatever is cached.
  if (isError && totalLength === 0) {
    return <ErrorState message={error?.message ?? 'Tapestry failed'} onRetry={refetch} />
  }

  if (!firstPageSettled && totalLength === 0) {
    return <LoadingView />
  }

  if (firstPageSettled && totalLength === 0) {
    return <QuietState />
  }

  // Drive history pagination from scroll position rather than a list
  // virtualizer's onEndReached — the wall is one Text node, not a
  // sequence of cells, so there is no list to call back from.
  const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
    if (!hasNextPage || isFetchingNextPage) return
    const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent
    const distanceFromBottom =
      contentSize.height - (contentOffset.y + layoutMeasurement.height)
    if (distanceFromBottom < 200) fetchNextPage()
  }

  return (
    <ScrollView
      flex={1}
      onScroll={onScroll}
      scrollEventThrottle={64}
      contentInsetAdjustmentBehavior={scrollProps.contentInsetAdjustmentBehavior}
      contentInset={scrollProps.contentInset}
      scrollIndicatorInsets={scrollProps.scrollIndicatorInsets}
      contentContainerStyle={scrollProps.contentContainerStyle}
    >
      <YStack paddingHorizontal="$4" paddingVertical="$2">
        <WeaveText body={weave} pending={pending} />
      </YStack>
    </ScrollView>
  )
}

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>
  )
}
