import { useEffect } from 'react';
import { App } from '@capacitor/app';
import { isNativePlatform } from '../utils/capacitor';
import { useMatrixClient } from './useMatrixClient';

const PENDING_PREFIX = 'vojo.pendingDeclines.';

// Drains the tombstones CallDeclineReceiver leaves behind when its HTTP PUT
// fails (network drop, 4xx/5xx, process killed mid-request). Matches the
// native-side contract in CallDeclineReceiver.java — the receiver writes the
// tombstone BEFORE the PUT and removes it only on 2xx.
//
// Runs on mount (covers "app opened after a failed receiver path") and on
// Capacitor `App` resume (covers "receiver ran while app was paused, HTTP
// died, user unlocks and brings app to foreground").
//
// Dedup: the receiver generates its own txnId; retries here generate another
// via matrix-js-sdk's sendRtcDecline → cosmetic chance of two decline events
// in the timeline if the receiver actually succeeded but the process was
// killed before clearing the tombstone. A-side auto-hangup is idempotent on
// the first decline, so this is timeline-clutter only. Fixing requires
// plumbing txnId through prefs, skipped for MVP.
export const usePendingDeclinesFlusher = (): void => {
  const mx = useMatrixClient();

  useEffect(() => {
    if (!isNativePlatform()) return undefined;

    let disposed = false;
    let inFlight = false;
    let resumeHandle: { remove: () => Promise<void> } | undefined;

    // Mount + an immediate `resume` (e.g. receiver fired, app foregrounded
    // right after the hook remounts) can both call drain() before the first
    // run finishes its per-key `get → sendRtcDecline → remove` loop. Without
    // this lock both passes observe the same tombstone and fire parallel
    // sendRtcDecline requests — benign at the server (idempotent on the
    // rel event_id) but it doubles the timeline noise.
    const drain = async () => {
      if (inFlight) return;
      inFlight = true;
      try {
        const { Preferences } = await import('@capacitor/preferences');
        const { keys } = await Preferences.keys();
        const tombstones = keys.filter((k) => k.startsWith(PENDING_PREFIX));
        // Sequential drain is intentional — see the race-condition rationale
        // in the comment above the hook (paralleling sendRtcDecline doubles
        // timeline noise on the A-side). Don't convert to Promise.all.
        /* eslint-disable no-restricted-syntax, no-await-in-loop, no-continue */
        for (const key of tombstones) {
          if (disposed) return;
          const notifEventId = key.slice(PENDING_PREFIX.length);
          try {
            const { value } = await Preferences.get({ key });
            if (!value) {
              await Preferences.remove({ key });
              continue;
            }
            const parsed = JSON.parse(value) as { roomId?: string };
            if (!parsed.roomId || !notifEventId) {
              await Preferences.remove({ key });
              continue;
            }
            await mx.sendRtcDecline(parsed.roomId, notifEventId);
            await Preferences.remove({ key });
          } catch (err) {
            // Leave tombstone for the next resume — could be 401 (token
            // rotated) or transient network. Don't spam logs on every resume.
            // eslint-disable-next-line no-console
            console.warn('[call] pendingDeclines flush failed', notifEventId, err);
          }
        }
        /* eslint-enable no-restricted-syntax, no-await-in-loop, no-continue */
      } catch {
        /* preferences import failed — non-fatal */
      } finally {
        inFlight = false;
      }
    };

    drain();

    App.addListener('resume', () => {
      drain();
    }).then((h) => {
      if (disposed) h.remove();
      else resumeHandle = h;
    });

    return () => {
      disposed = true;
      resumeHandle?.remove();
    };
  }, [mx]);
};
