import * as Network from 'expo-network'
import {useSyncExternalStore} from 'react'
import {AppState} from 'react-native'

// connectivity as a trigger, never a gate. on ios expo-network reports
// interface state only (isInternetReachable just mirrors isConnected) and can
// emit a stale status on a flap, so requests always attempt and classify their
// own failures — this only drives the offline banner and pokes deferred work
// (refreshes now, the outbox later) to run sooner when the link returns

// undefined is unknown, never offline: we must never block on a maybe
const isOffline = (state: Network.NetworkState): boolean =>
  state.isConnected === false

// one native subscription fans out to every consumer; started lazily on first
// use and seeded once, since the listener fires on transitions only
let offline = false
let started = false
const subscribers = new Set<(offline: boolean) => void>()

const setOffline = (next: boolean): void => {
  if (next === offline) return
  offline = next
  for (const notify of subscribers) notify(next)
}

const start = (): void => {
  if (started) return
  started = true
  void Network.getNetworkStateAsync()
    .then((state) => setOffline(isOffline(state)))
    .catch(() => {})
  Network.addNetworkStateListener((state) =>
    setOffline(isOffline(state)),
  )
}

const subscribe = (
  notify: (offline: boolean) => void,
): (() => void) => {
  start()
  subscribers.add(notify)
  return () => {
    subscribers.delete(notify)
  }
}

// whether the device has no network interface. drives ambient ui only; a false
// here is never a promise that a given request will succeed
export const useOffline = (): boolean =>
  useSyncExternalStore(
    (onChange) => subscribe(() => onChange()),
    () => offline,
  )

// run cb on an offline→online transition: the retry/flush trigger. never gates
// a request — worst case cb runs while still offline, fails harmlessly, and
// runs again on the real transition
export const onReconnect = (cb: () => void): (() => void) =>
  subscribe((nowOffline) => {
    if (!nowOffline) cb()
  })

// run cb whenever the app is most likely to have just regained reach: on
// foreground and on reconnect. the trigger for deferred work — a screen's
// refresh, the outbox flush — never a gate. returns an unsubscribe
export const onActive = (cb: () => void): (() => void) => {
  const sub = AppState.addEventListener('change', (state) => {
    if (state === 'active') cb()
  })
  const off = onReconnect(cb)
  return () => {
    sub.remove()
    off()
  }
}
