/* === useExpenseProposals(householdId, { demoSeed }) ===
   The Apple Pay tray. Reads PENDING rows from `expense_proposals`
   (migration 0011), which the wallet-inbound Edge Function fills from
   the "Log to Homebook" iOS Shortcut. This hook never creates an
   expense — LedgerApp's walletOps.confirm does that, after resolve()
   has claimed the proposal.

   Returns:
     {
       proposals,                 // pending only, newest first
       resolve(id, status, expenseId?) → true if THIS call claimed it,
                                  // false if someone else got there first
       refresh(),
       loading,
       error,
     }

   Claiming: resolve() updates `WHERE id = … AND status = 'pending'`
   and reads back the row. Two partners confirming the same tap at
   once → exactly one sees `true`, so exactly one expense is written.

   Visibility: RLS hides a partner's taps when they've switched
   sharing off. Realtime can't tell us a row just became invisible,
   so we also re-fetch when the window regains focus.

   Demo mode (no householdId): runs entirely in memory off demoSeed,
   so the tray can be seen and exercised without Supabase.
*/

function proposalFromRow(row) {
  return {
    id: row.id,
    payerUserId: row.payer_user_id,
    source: row.source,
    rawMerchant: row.raw_merchant,
    amount: Number(row.amount),
    cardLabel: row.card_label || '',
    date: row.occurred_on,
    visibility: row.visibility,
    createdAt: row.created_at,
  };
}

const sortProposals = (list) =>
  [...list].sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));

function useExpenseProposals(householdId, opts) {
  const demoSeed = opts && opts.demoSeed;
  const [proposals, setProposals] = React.useState(() => (!householdId && demoSeed) ? sortProposals(demoSeed) : []);
  const [loading, setLoading] = React.useState(!!householdId);
  const [error, setError] = React.useState(null);

  // ids we just resolved locally. Unlike a bare Set, entries expire, so
  // an echo that never arrives (dropped socket) can't swallow a later
  // real event for the same row.
  const pendingIds = React.useRef(new Map());
  const markPending = (id) => {
    const prev = pendingIds.current.get(id);
    if (prev) clearTimeout(prev);
    pendingIds.current.set(id, setTimeout(() => pendingIds.current.delete(id), 10000));
  };
  const takePending = (id) => {
    const t = pendingIds.current.get(id);
    if (!t) return false;
    clearTimeout(t);
    pendingIds.current.delete(id);
    return true;
  };

  const load = React.useCallback(async () => {
    if (!householdId) return;
    const { data, error } = await supabaseClient
      .from('expense_proposals')
      .select('*')
      .eq('household_id', householdId)
      .eq('status', 'pending')
      .order('created_at', { ascending: false });
    if (error) { setError(error.message); setLoading(false); return; }
    setProposals((data || []).map(proposalFromRow));
    setError(null);
    setLoading(false);
  }, [householdId]);

  React.useEffect(() => {
    if (!householdId) return;
    setLoading(true);
    load();
  }, [householdId, load]);

  React.useEffect(() => {
    if (!householdId) return;
    const upsert = (row) => setProposals(prev => {
      if (row.status !== 'pending') return prev.filter(p => p.id !== row.id);
      const next = proposalFromRow(row);
      return sortProposals(prev.some(p => p.id === row.id)
        ? prev.map(p => p.id === row.id ? next : p)
        : [next, ...prev]);
    });
    const channel = supabaseClient
      .channel(`expense_proposals:${householdId}`)
      .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'expense_proposals', filter: `household_id=eq.${householdId}` },
          (payload) => upsert(payload.new))
      .on('postgres_changes',
          { event: 'UPDATE', schema: 'public', table: 'expense_proposals', filter: `household_id=eq.${householdId}` },
          (payload) => {
            if (takePending(payload.new.id)) return;
            upsert(payload.new);
          })
      .subscribe((status) => {
        // Catch up on anything missed while the socket was down.
        if (status === 'SUBSCRIBED') load();
      });
    const onVisible = () => { if (document.visibilityState === 'visible') load(); };
    document.addEventListener('visibilitychange', onVisible);
    return () => {
      document.removeEventListener('visibilitychange', onVisible);
      supabaseClient.removeChannel(channel);
    };
  }, [householdId, load]);

  const resolve = React.useCallback(async (id, status, expenseId) => {
    const snapshot = proposals.find(p => p.id === id) || null;
    setProposals(prev => prev.filter(p => p.id !== id));
    if (!householdId) return true; // demo: purely local
    markPending(id);
    const { data, error } = await supabaseClient
      .from('expense_proposals')
      .update({
        status,
        expense_id: expenseId || null,
        resolved_at: new Date().toISOString(),
        resolved_by: (await supabaseClient.auth.getUser()).data?.user?.id || null,
      })
      .eq('id', id)
      .eq('status', 'pending')
      .select('id');
    if (error) {
      takePending(id);
      if (snapshot) setProposals(prev => sortProposals([snapshot, ...prev.filter(p => p.id !== id)]));
      setError(error.message);
      throw error;
    }
    setError(null);
    // Zero rows back = a partner resolved it a moment before us. The
    // row is gone from our tray either way; tell the caller not to
    // write a second expense.
    return (data || []).length > 0;
  }, [householdId, proposals]);

  return { proposals, resolve, refresh: load, loading, error };
}

Object.assign(window, { useExpenseProposals });
