import {useRouter} from 'expo-router'
import {useEffect, useRef} from 'react'
import {AccessibilityInfo, Pressable, View} from 'react-native'
import Animated, {FadeIn} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {FullWindowOverlay} from 'react-native-screens'

import {Text} from '@/components/ui/typography'
import {useDelayedFlag} from '@/hooks/use-delayed-flag'
import {useOffline} from '@/lib/network'
import {atoms as a, tokens, useTheme} from '@/sprinkles'
import {
  SYNC_STATUS_DELAY_MS,
  useFailedCount,
  usePendingCount,
} from '@/stores/outbox'

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

// an ios large-title header adds ~52pt of title below the nav bar
const LARGE_TITLE_HEIGHT = 52
// how far below the safe-area top the tab sits: a small gap, then clear a
// large-title header where a screen has one so the tab never lands on the title
const TOP_INSET = 24 + LARGE_TITLE_HEIGHT

// the ambient connectivity + sync status tab: a small pill wedged into the left
// edge near the top, its label rotated to read down the edge. it surfaces three
// states and hides when there's nothing to say (online and fully synced):
//
//   offline           → "Offline" / "Offline (n)" with n unsynced changes
//   syncing (online)  → "Syncing (n)" while the outbox drains
//   failed  (online)  → "Sync failed (n)", tinted, needing a manual retry
//
// when there are queued changes the tab is a button that opens the pending-
// changes screen; with nothing queued it's a passive indicator. connectivity is
// a signal, not a gate — the app stays interactive and cached data keeps
// showing; this only sets expectations. pointer-events pass through the empty
// area so it never eats a tap.
//
// FullWindowOverlay hoists it to the ios window, so it sits above every screen
// including natively-presented modals. `transformOrigin: 'left bottom'` pins the
// rotation pivot to the screen edge, so the flush side always lands at x=0 no
// matter how long the label grows with the user's type size
export const OfflineBanner = () => {
  const offline = useOffline()
  const pending = usePendingCount()
  const failed = useFailedCount()
  const insets = useSafeAreaInsets()
  const router = useRouter()
  const t = useTheme()

  const queued = pending + failed
  const failedMode = !offline && failed > 0

  // gate only the pure-syncing appearance so a fast drain never flashes the
  // tab; offline and failed states persist and stay immediate
  const syncing = !offline && failed === 0 && pending > 0
  const showSyncing = useDelayedFlag(syncing, SYNC_STATUS_DELAY_MS)

  const label = offline
    ? queued > 0
      ? `Offline (${queued})`
      : 'Offline'
    : failed > 0
      ? `Sync failed (${failed})`
      : `Syncing (${pending})`

  // announce both edges of the transition (guarding the initial mount) —
  // voiceover users should hear recovery, not just the outage
  const wasOffline = useRef(false)
  useEffect(() => {
    if (offline) AccessibilityInfo.announceForAccessibility('Offline')
    else if (wasOffline.current)
      AccessibilityInfo.announceForAccessibility('Back online')
    wasOffline.current = offline
  }, [offline])

  useEffect(() => {
    if (failedMode)
      AccessibilityInfo.announceForAccessibility(
        'Some changes failed to sync',
      )
  }, [failedMode])

  // online and fully synced, or a sync too brief to have gated: nothing to show
  if (!offline && failed === 0 && !showSyncing) return null

  const interactive = queued > 0

  return (
    // the full-screen container rn-screens adds to the window defaults to
    // accessibilityViewIsModal, which makes voiceover ignore the whole app
    // beneath the banner — opt out so the app stays navigable while it shows
    <FullWindowOverlay
      unstable_accessibilityContainerViewIsModal={false}
    >
      <View
        pointerEvents={interactive ? 'box-none' : 'none'}
        style={[a.absolute, {left: 0, top: insets.top + TOP_INSET}]}
      >
        <AnimatedPressable
          entering={FadeIn.duration(220)}
          disabled={!interactive}
          onPress={
            interactive
              ? () => router.push('/pending-changes')
              : undefined
          }
          accessibilityRole={interactive ? 'button' : 'text'}
          accessibilityLabel={label}
          accessibilityHint={
            interactive ? 'View unsynced changes' : undefined
          }
          style={[
            {
              paddingVertical: 3,
              paddingHorizontal: 8,
              borderTopLeftRadius: tokens.radius.sm,
              borderTopRightRadius: tokens.radius.sm,
              transform: [{rotate: '90deg'}],
              transformOrigin: 'left bottom',
              shadowColor: '#000',
              shadowOpacity: 0.12,
              shadowRadius: 5,
              shadowOffset: {width: 2, height: 1},
              backgroundColor: failedMode
                ? t.palette.negative_500
                : t.atoms.bg_contrast_900.backgroundColor,
            },
          ]}
        >
          <Text
            style={[a.text_xxs, a.font_medium, t.atoms.text_inverted]}
          >
            {label}
          </Text>
        </AnimatedPressable>
      </View>
    </FullWindowOverlay>
  )
}
