// Root layout — wires the Voltro client, deep-link routing, and a connection
// banner around the whole app.
//
// The boot ORDER here is load-bearing, and it is the one thing to preserve if
// you rewrite this file:
//
//   1. hydrate persisted stores, THEN install them as the backing store;
//   2. only then render the app.
//
// A store reads during render and a render cannot await, so rendering first
// shows empty state and flickers into the saved state a frame later. The splash
// screen stays up until step 1 finishes — that is what it is for.
//
// Connecting is deliberately NOT part of that gate. The app must open offline.
import { useEffect, useState } from 'react'
import { Stack, useRouter } from 'expo-router'
import * as Linking from 'expo-linking'
import * as SplashScreen from 'expo-splash-screen'
import { StatusBar } from 'expo-status-bar'
import { FrameworkRuntimesProvider, type ApiHandle, type ResolvedClient } from '@voltro/client'
import { useMobileConnectionStatus } from '@voltro/react-native'
import { Text, View } from 'react-native'
import { connectApis, handlesFor, onlineSource, preparePersistence } from '../client'
import { handleDeepLink, makeDeepLinks } from '../lib/deeplinks'

void SplashScreen.preventAutoHideAsync()

function ConnectionBanner() {
  // Reachability comes from NetInfo. The default source reads `navigator.onLine`,
  // which React Native does not have — so on a device it would answer "online"
  // forever, including in airplane mode.
  const { status } = useMobileConnectionStatus({ onlineSource })
  if (status === 'connected') return null
  return (
    <View style={{ backgroundColor: status === 'offline' ? '#b91c1c' : '#b45309', padding: 8 }}>
      <Text style={{ color: 'white', textAlign: 'center' }}>
        {status === 'offline' ? 'Offline — changes will sync when you reconnect' : 'Reconnecting…'}
      </Text>
    </View>
  )
}

export default function RootLayout() {
  const [ready, setReady] = useState(false)
  const [apis, setApis] = useState<ReadonlyMap<string, ApiHandle>>(() => new Map())
  const router = useRouter()

  // 1. Persisted state, before anything renders.
  useEffect(() => {
    let live = true
    void preparePersistence()
      .catch(() => { /* a device with no readable storage still gets an app */ })
      .finally(() => {
        if (!live) return
        setReady(true)
        void SplashScreen.hideAsync()
      })
    return () => { live = false }
  }, [])

  // 2. Connect, and keep connected. The supervisor re-dials with backoff; a
  //    dropped socket needs no handling here.
  useEffect(() => {
    const supervisor = connectApis((clients: ReadonlyMap<string, ResolvedClient>) => {
      setApis(handlesFor(clients))
    })
    return () => supervisor.dispose()
  }, [])

  // Route inbound URLs (universal links, custom scheme, push taps) through the
  // ONE typed link table — the same `matchFirstDeepLink` path push will use.
  useEffect(() => {
    const links = makeDeepLinks((href) => router.push(href as never))
    const sub = Linking.addEventListener('url', ({ url }) => {
      handleDeepLink(links, url)
    })
    void Linking.getInitialURL().then((url) => {
      if (url) handleDeepLink(links, url)
    })
    return () => sub.remove()
  }, [router])

  if (!ready) return null

  return (
    <FrameworkRuntimesProvider apis={apis}>
      <StatusBar style="auto" />
      <ConnectionBanner />
      <Stack screenOptions={{ headerTitle: '{{capProjectName}}' }} />
    </FrameworkRuntimesProvider>
  )
}
