import { useEffect, useState } from 'react';
import { App } from '@capacitor/app';
import { isAndroidPlatform, isIOSPlatform } from '../utils/capacitor';

// Single source of truth for "is the app foreground / active right
// now". Consolidates the App.pause / App.resume / App.appStateChange
// listeners that previously lived independently in `SyncIndicator`,
// `IncomingCallStripRenderer`, and `useSyncPresenceGate` — each used
// to register its own native bridge listener, so a single
// pause/resume event was reaching JS three times.
//
// Implementation: a module-level singleton owns one set of Capacitor
// listeners. React consumers subscribe via the `useAppActive` hook;
// when the subscriber count drops to zero, the native listeners are
// torn down. This keeps the WebView↔native bridge quiet while still
// letting any number of React components observe the active state.
//
// Platform branching:
//   - Android: `App.pause` / `App.resume` fire on Activity onPause /
//     onResume (ms latency). `appStateChange` ties to onStop which is
//     10-1000 ms later — too slow for ring-audio handoff and noisy
//     for /sync set_presence updates.
//   - iOS: same `pause` / `resume` pair binds to
//     didEnterBackground / willEnterForeground. We deliberately do
//     NOT use `appStateChange` on iOS because that maps to
//     willResignActive — which fires for transient interruptions
//     (Control Center swipe, app-switcher peek, incoming call
//     prompt). For presence/sync purposes those interruptions are
//     noise; we want only real backgrounding to flip state.
//   - Web: no `pause` / `resume` equivalent — fall back to
//     `appStateChange`, which Capacitor maps to
//     `document.visibilitychange`.

type ActiveListener = (active: boolean) => void;

let currentActive: boolean | null = null;
const subscribers = new Set<ActiveListener>();
let nativeHandles: Array<{ remove: () => void }> = [];
let bootstrap: Promise<void> | null = null;

const readInitialActive = (): boolean => {
  if (typeof document === 'undefined') return true;
  return document.visibilityState !== 'hidden';
};

const broadcast = (next: boolean) => {
  if (currentActive === next) return;
  currentActive = next;
  subscribers.forEach((cb) => cb(next));
};

const ensureNativeListeners = async (): Promise<void> => {
  if (bootstrap) return bootstrap;
  bootstrap = (async () => {
    // Seed before listeners attach so an immediately-subscribed
    // consumer can read a meaningful value via the initial useState
    // initializer; the async getState below corrects it on native.
    if (currentActive === null) currentActive = readInitialActive();

    const sawLifecycleEventRef = { current: false };
    const apply = (isActive: boolean) => {
      sawLifecycleEventRef.current = true;
      broadcast(isActive);
    };

    const pendingAttach: Array<Promise<{ remove: () => void }>> = [];
    if (isAndroidPlatform() || isIOSPlatform()) {
      pendingAttach.push(App.addListener('pause', () => apply(false)));
      pendingAttach.push(App.addListener('resume', () => apply(true)));
    } else {
      pendingAttach.push(App.addListener('appStateChange', ({ isActive }) => apply(isActive)));
    }

    try {
      nativeHandles = await Promise.all(pendingAttach);
    } catch (err) {
      // eslint-disable-next-line no-console
      console.warn('[useAppActive] native listener registration failed', err);
      nativeHandles = [];
    }

    // Authoritative initial value from native, but only if no real
    // lifecycle event has landed in the meantime — events always win
    // over a stale snapshot (mirrors the pattern that used to live
    // inline in IncomingCallStripRenderer).
    try {
      const state = await App.getState();
      if (!sawLifecycleEventRef.current) broadcast(state.isActive);
    } catch {
      // Web has no native getState; document.visibilityState seed
      // above is enough.
    }
  })();
  return bootstrap;
};

const teardownNativeListeners = () => {
  nativeHandles.forEach((h) => h.remove());
  nativeHandles = [];
  bootstrap = null;
  currentActive = null;
};

const subscribe = (cb: ActiveListener): (() => void) => {
  subscribers.add(cb);
  ensureNativeListeners().catch(() => {
    // already logged inside ensureNativeListeners
  });
  // Hand the current value to a brand-new subscriber so it doesn't
  // need to wait for the next lifecycle event.
  if (currentActive !== null) cb(currentActive);
  return () => {
    subscribers.delete(cb);
    if (subscribers.size === 0) teardownNativeListeners();
  };
};

/**
 * Returns `true` while the app is foregrounded / the tab is visible,
 * `false` while backgrounded / hidden. See module header for the
 * platform-event mapping.
 */
export const useAppActive = (): boolean => {
  const [active, setActive] = useState<boolean>(() => currentActive ?? readInitialActive());

  useEffect(() => subscribe(setActive), []);

  return active;
};
