// Top-level renderer for incoming DM call strips + ringtone audio.
//
// Mounted in Router.tsx inside `CallEmbedProvider`, rendered right before
// `CallStatusRenderer` so the strip stacks above the in-call pill.
//
// IncomingCallStripRenderer is platform-agnostic: if JS knows about an incoming
// ring, we render the in-app strip. On Android the native FCM service decides
// independently whether to surface a system CallStyle notification; when the
// app is foregrounded it suppresses that banner, so strip render itself does
// not need to mirror foreground policy in JS.
//
// Ring audio mirrors foreground policy on Android — gated on `appActive`
// so the native CallStyle ringtone owns UX in background and the JS
// <audio> doesn't double-ring during the grace window after backgrounding
// while the WebView still processes /sync. On web / iOS there is no
// native ring surface, so audio plays regardless of visibility.
//
// Known gap: if the browser blocks `audio.play()` (cold page load, no user
// gesture yet), the ring is silent — strip is still visible but user may
// miss it. Fallback (click-to-enable, pulsing animation, Web Notifications)
// is Phase 3 polish.

import React, { useEffect, useRef, useState } from 'react';
import { useAtomValue } from 'jotai';
import { Box } from 'folds';
import { App } from '@capacitor/app';
import { incomingCallsAtom } from '../state/incomingCalls';
import { useMatrixClient } from '../hooks/useMatrixClient';
import { IncomingCallStrip } from '../features/call-status';
import { isAndroidPlatform } from '../utils/capacitor';
// eslint-disable-next-line import/no-relative-packages
import RingSoundOgg from '../../../public/sound/ring.ogg';
// eslint-disable-next-line import/no-relative-packages
import RingSoundMp3 from '../../../public/sound/ring.mp3';

export function IncomingCallStripRenderer() {
  const mx = useMatrixClient();
  const incoming = useAtomValue(incomingCallsAtom);
  const audioRef = useRef<HTMLAudioElement>(null);
  const [appActive, setAppActive] = useState(
    () => typeof document === 'undefined' || document.visibilityState === 'visible'
  );

  const hasIncoming = incoming.size > 0;

  useEffect(() => {
    let cancelled = false;
    // Android: pause/resume fire on BridgeActivity.onPause/onResume via
    // AppPlugin.handleOnPause/Resume — same Activity callbacks that flip
    // MainActivity.isInForeground — reaching JS within ms via the WebView
    // bridge. appStateChange on Android only fires in onStop (tens of ms to
    // ~1s after onPause), which would let JS audio race the native ringtone
    // in the "just backgrounded" window and double-ring.
    // iOS/web keeps appStateChange because there it maps to
    // willResignActive/didBecomeActive (iOS) or document.visibilitychange
    // (web). The Capacitor pause/resume events on iOS bind to
    // didEnterBackground/willEnterForeground — a different edge.
    // Race-guard: a real lifecycle event arriving before getState() resolves
    // must win over the stale snapshot.
    let sawLifecycleEvent = false;
    const handles: Array<{ remove: () => void }> = [];
    const track = (h: { remove: () => void }) => {
      if (cancelled) h.remove();
      else handles.push(h);
    };

    const apply = (isActive: boolean) => {
      sawLifecycleEvent = true;
      if (!cancelled) setAppActive(isActive);
    };

    if (isAndroidPlatform()) {
      App.addListener('pause', () => apply(false)).then(track);
      App.addListener('resume', () => apply(true)).then(track);
    } else {
      App.addListener('appStateChange', ({ isActive }) => apply(isActive)).then(track);
    }

    App.getState()
      .then((state) => {
        if (!cancelled && !sawLifecycleEvent) setAppActive(state.isActive);
      })
      .catch(() => {
        /* web fallback handled by initial document.visibilityState */
      });

    return () => {
      cancelled = true;
      handles.forEach((h) => h.remove());
    };
  }, []);

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    // Platform split on the audio gate:
    // - Android: gate on appActive. When backgrounded the native CallStyle
    //   ringtone (via vojo_calls_v2 channel) takes over, so JS audio must
    //   stop to avoid double-ring.
    // - web / iOS: no native fallback exists. Gating on visibility here
    //   silenced the only ring source whenever the user switched tabs —
    //   user-reported regression. Keep audio playing regardless of
    //   visibility on non-Android platforms.
    const platformGatedActive = isAndroidPlatform() ? appActive : true;
    if (hasIncoming && platformGatedActive) {
      audio.currentTime = 0;
      audio.play().catch(() => {
        // autoplay blocked — strip UI still visible
      });
    } else {
      audio.pause();
    }
  }, [hasIncoming, appActive]);

  const entries = Array.from(incoming.values());

  return (
    <>
      {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
      <audio ref={audioRef} loop preload="auto" style={{ display: 'none' }}>
        <source src={RingSoundOgg} type="audio/ogg" />
        <source src={RingSoundMp3} type="audio/mpeg" />
      </audio>
      {hasIncoming && (
        <Box direction="Column" shrink="No">
          {entries.map((call) => {
            const room = mx.getRoom(call.roomId);
            if (!room) return null;
            return (
              <IncomingCallStrip
                key={`call_${call.callId}_${call.roomId}`}
                call={call}
                room={room}
              />
            );
          })}
        </Box>
      )}
    </>
  );
}
