"use client";

import React, { Suspense, useEffect, useMemo, useRef, useState } from "react";
import { useAuth } from "@/hooks/useAuth";
import { useRouter, useSearchParams } from "next/navigation";
import { apiFetch, fetchAllUsers, fetchAllPayments } from "@/lib/api";
import { useDismissingState } from "@/hooks/useDismissingState";
import { scoreUser } from "@/lib/fuzzyMatch";
import { Wallet, RotateCcw, HandHeart, ScanLine, Link2 } from "lucide-react";

// A donation is never mutated once created — assigning it to a registration creates a separate
// "leg" Payment row (isDonation:false, originalPaymentId -> the donation, amount > 0) instead.
// That leg is not new money: it just re-labels part of an already-counted donation as applied
// to a registration. Money stats/lists must count each real inflow exactly once, so legs are
// excluded — the money was already counted via the original donation row.
function isDonationLeg(p: any): boolean {
  return !p?.isDonation && !!p?.originalPaymentId && (p?.amount || 0) > 0;
}

// Refund methods must mirror the real payment methods (cash/card/eft/voucher) so a refund
// nets against the same bucket its original payment counted under in reports — a card payment
// refunded as "cash-refund" would wrongly drain the cash float and leave card overstated.
function refundMethodForOriginal(method: string | null | undefined): string {
  const m = String(method || "").toLowerCase();
  if (m.includes("cash")) return "cash-refund";
  if (m.includes("eft")) return "eft-refund";
  if (m.includes("voucher")) return "voucher-refund";
  if (m.includes("card") || m.includes("yoco") || m.includes("pay")) return "card-refund";
  return "";
}

function RegistrationOptions({ regs, regOutstanding }: {
  regs: any[];
  regOutstanding: Record<string, { totalDue: number; totalPaid: number; outstanding: number }>;
}) {
  return (
    <>
      {regs.map((r: any) => {
        const out = regOutstanding[r.id]?.outstanding ?? 0;
        const label = `${r.event?.title || r.eventId || 'Event'} — ${r.user?.name || r.userId || 'User'} — Outstanding: R ${out.toFixed(2)} — #${String(r.id).slice(0,8)}`;
        return (
          <option key={r.id} value={r.id} title={label}>
            {label}
          </option>
        );
      })}
    </>
  );
}

function UserSearchField({ allUsers, value, onChange, placeholder = "Search by name, email or phone…", disabled = false }: {
  allUsers: any[];
  value: string;
  onChange: (userId: string) => void;
  placeholder?: string;
  disabled?: boolean;
}) {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  // Sync display name when value changes externally (e.g. pre-fill from metadata)
  useEffect(() => {
    const u = allUsers.find(u => String(u.id) === String(value));
    setQuery(u?.name || "");
  }, [value, allUsers]);

  const matches = useMemo(() => {
    if (query.trim().length < 2) return [];
    return allUsers
      .map(u => ({ u, score: scoreUser(u, query) }))
      .filter(x => x.score >= 0.45)
      .sort((a, b) => b.score - a.score)
      .slice(0, 8)
      .map(x => x.u);
  }, [query, allUsers]);

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  return (
    <div ref={ref} className="relative">
      <input
        className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400 disabled:bg-gray-50 pr-7"
        placeholder={placeholder}
        value={query}
        disabled={disabled}
        autoComplete="off"
        onChange={e => { setQuery(e.target.value); setOpen(true); if (!e.target.value) onChange(""); }}
        onFocus={() => { if (query.length >= 2) setOpen(true); }}
      />
      {value && (
        <button type="button" className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 text-xs" onClick={() => { onChange(""); setQuery(""); setOpen(false); }}>✕</button>
      )}
      {open && query.trim().length >= 2 && (
        <div className="absolute z-30 top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden">
          {matches.length > 0 ? (
            <>
              <div className="px-3 py-1.5 text-[11px] text-gray-400 bg-gray-50 border-b">{matches.length} result{matches.length !== 1 ? "s" : ""}</div>
              {matches.map(u => (
                <button key={u.id} type="button" className="w-full text-left px-3 py-2.5 hover:bg-brand-50 border-b border-gray-100 last:border-b-0 transition-colors" onClick={() => { onChange(String(u.id)); setQuery(u.name || ""); setOpen(false); }}>
                  <div className="text-sm font-medium text-gray-900">{u.name}</div>
                  <div className="text-xs text-gray-500 mt-0.5 flex gap-2 flex-wrap">
                    {u.email && <span>{u.email}</span>}
                    {u.phoneNumber && <span>· {u.phoneNumber}</span>}
                  </div>
                </button>
              ))}
            </>
          ) : (
            <div className="px-3 py-3 text-sm text-gray-500 italic">No matching users found.</div>
          )}
        </div>
      )}
    </div>
  );
}

function PaymentsContent() {
  const { user, loading, token } = useAuth();
  const router = useRouter();
  const search = useSearchParams();

  const canView = useMemo(() => {
    const role = user?.role;
    return role === "admin" || role === "supervisor";
  }, [user]);

  useEffect(() => {
    if (loading) return;
    if (!user) router.replace("/login");
  }, [user, loading, router]);

  const [payments, setPayments] = useState<any[]>([]);
  const [loadingList, setLoadingList] = useState(false);
  const [error, setError] = useDismissingState<string | null>(null);
  const [info, setInfo] = useDismissingState<string | null>(null);
  const [registrations, setRegistrations] = useState<any[]>([]);
  const [loadingRegs, setLoadingRegs] = useState(false);
  const [regOutstanding, setRegOutstanding] = useState<Record<string, { totalDue: number; totalPaid: number; outstanding: number }>>({});

  const loadPayments = async () => {
    if (!token) return;
    try {
      setLoadingList(true);
      const list = await fetchAllPayments(token);
      setPayments(list.sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()));
    } catch (e: any) {
      setError(e?.message || "Failed to load payments");
    } finally {
      setLoadingList(false);
    }
  };

  useEffect(() => { loadPayments(); }, [token]);

  // Compute effective unit price for a registration option.
  // Uses priceSnapshot when available (authoritative backend price, variant-aware).
  // Falls back to deadline-only early-bird calculation for legacy rows without a snapshot.
  const optionUnitPrice = (opt: any, referenceTime: any, atTime: Date): number => {
    if (opt.priceSnapshot !== null && opt.priceSnapshot !== undefined) {
      return Number(opt.priceSnapshot);
    }
    const eo = opt.eventOption;
    const variantId: string | null = opt.variantId || null;
    const base = (opt.variant?.price !== null && opt.variant?.price !== undefined)
      ? Number(opt.variant.price)
      : Number(eo?.price || 0);
    const allTiers = Array.isArray(eo?.earlyBirdTiers) ? eo.earlyBirdTiers.slice() : [];
    const tiers = variantId
      ? allTiers.filter((t: any) => t.variantId === variantId)
      : allTiers.filter((t: any) => !t.variantId);
    if (tiers.length === 0) return base;
    const t = atTime ? new Date(atTime) : new Date();
    const ref = referenceTime ? new Date(referenceTime) : t;
    const applicable = tiers
      .map((x: any) => ({ ...x, deadline: new Date(x.deadline) }))
      .filter((x: any) => (ref < x.deadline) && (t < x.deadline))
      .sort((a: any, b: any) => a.deadline.getTime() - b.deadline.getTime() || (a.order||0) - (b.order||0) || a.price - b.price);
    if (applicable.length === 0) return base;
    const price = Number(applicable[0].price);
    return (price >= 0) ? price : base;
  };

  // Load registrations for dropdowns.
  // GET /api/registrations already embeds each registration's `payments`, so outstanding
  // balances are computed from that in one pass — no per-registration follow-up requests.
  const loadRegistrations = async () => {
    if (!token) return;
    try {
      setLoadingRegs(true);
      const regs = await apiFetch<any[]>("/api/registrations", { authToken: token });
      const list = Array.isArray(regs) ? regs : [];
      const now = new Date();
      const map: Record<string, { totalDue: number; totalPaid: number; outstanding: number }> = {};
      for (const r of list) {
        // Backend attaches a tranche-aware totalDueComputed (exact even when a line spans
        // multiple early-bird prices) — fall back to the old client-side estimate otherwise.
        const totalDue = r.totalDueComputed ?? (r.registrationOptions || []).reduce((sum: number, opt: any) => sum + optionUnitPrice(opt, null, now) * (opt.quantity || 0), 0);
        const totalPaid = (r.payments || []).reduce((s: number, p: any) => s + (p.amount || 0), 0);
        map[r.id] = { totalDue, totalPaid, outstanding: Math.max(0, totalDue - totalPaid) };
      }
      setRegOutstanding(map);
      // Sort by createdAt desc
      const sorted = list.sort((a,b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
      setRegistrations(sorted);
    } catch (e) {
      // ignore
    } finally {
      setLoadingRegs(false);
    }
  };

  useEffect(() => { loadRegistrations(); }, [token]);

  // Load all users for Refund section
  useEffect(() => {
    (async () => {
      if (!token) return;
      try {
        setLoadingUsers(true);
        const users = await fetchAllUsers(token);
        setAllUsers(users);
      } catch (e) {
        // ignore; fallback to derived users from registrations
      } finally {
        setLoadingUsers(false);
      }
    })();
  }, [token]);

  // Create payment form
  const [amount, setAmount] = useState<string>("");
  const [method, setMethod] = useState<string>("cash");
  const [registrationId, setRegistrationId] = useState<string>("");
  const [isDonation, setIsDonation] = useState<boolean>(false);
  const [eventId, setEventId] = useState<string>("");
  const [events, setEvents] = useState<any[]>([]);
  const [submitting, setSubmitting] = useState(false);
  // Backdate field
  const [paidAtLocal, setPaidAtLocal] = useState<string>("");
  // Users for Refund section (load all users with supervisor rights)
  const [allUsers, setAllUsers] = useState<any[]>([]);
  const [loadingUsers, setLoadingUsers] = useState(false);

  // New: user selection states for split dropdowns
  const [selectedUserId, setSelectedUserId] = useState<string>("");

  // Derived lists for users and outstanding registrations
  const outstandingRegs = useMemo(() => {
    return registrations.filter((r: any) => (r.status !== 'paid') && (r.status !== 'cancelled') && ((regOutstanding[r.id]?.outstanding ?? 0) > 0));
  }, [registrations, regOutstanding]);

  // Users list for UI dropdowns (use all users if loaded; fallback to outstanding registrations)
  const usersList = useMemo(() => {
    if (allUsers && allUsers.length > 0) {
      return [...allUsers]
        //.filter(u => u?.isActive !== false)
        .map(u => ({ id: String(u.id), name: String(u.name || u.email || u.id) }))
        .sort((a, b) => a.name.localeCompare(b.name, undefined as any, { sensitivity: 'base' } as any));
    }
    const map = new Map<string, string>();
    for (const r of outstandingRegs) {
      const uid = r.user?.id || r.userId;
      if (!uid) continue;
      const name = r.user?.name || r.user?.email || String(r.userId || uid);
      if (!map.has(String(uid))) {
        map.set(String(uid), String(name));
      }
    }
    return Array.from(map.entries())
      .map(([id, name]) => ({ id, name }))
      .sort((a, b) => a.name.localeCompare(b.name, undefined as any, { sensitivity: 'base' } as any));
  }, [allUsers, outstandingRegs]);

  const regsForSelectedUser = useMemo(() => {
    return outstandingRegs.filter((r: any) => (String(r.user?.id || r.userId) === String(selectedUserId)));
  }, [outstandingRegs, selectedUserId]);

  // Payment link tab state
  const [linkUserId, setLinkUserId] = useState<string>("");
  const [linkRegistrationId, setLinkRegistrationId] = useState<string>("");
  const [linkAmount, setLinkAmount] = useState<string>("");
  const [linkGenerating, setLinkGenerating] = useState(false);
  const [linkResult, setLinkResult] = useState<{ redirectUrl: string; amount: number; registrationId: string } | null>(null);
  const [linkSending, setLinkSending] = useState<"email" | "whatsapp" | null>(null);
  const [linkCopied, setLinkCopied] = useState(false);

  const regsForLinkUser = useMemo(() => {
    return outstandingRegs.filter((r: any) => (String(r.user?.id || r.userId) === String(linkUserId)));
  }, [outstandingRegs, linkUserId]);

  useEffect(() => {
    if (!linkRegistrationId) return;
    const out = regOutstanding[linkRegistrationId]?.outstanding ?? 0;
    setLinkAmount(out > 0 ? out.toFixed(2) : "");
    setLinkResult(null);
  }, [linkRegistrationId, regOutstanding]);

  const generatePaymentLink = async () => {
    if (!token || !linkRegistrationId) return;
    setError(null); setInfo(null); setLinkCopied(false);
    const amt = parseFloat(linkAmount || "0");
    if (!amt || amt <= 0) { setError("Enter a valid amount"); return; }
    try {
      setLinkGenerating(true);
      const res = await apiFetch<{ redirectUrl?: string; checkoutId?: string; amount?: number; priceUpdated?: boolean; message?: string }>("/api/payments/yoco-checkout", {
        method: "POST",
        authToken: token,
        body: {
          registrationId: linkRegistrationId,
          amount: amt,
          successUrl: window.location.origin + "/payment/success",
          cancelUrl: window.location.origin + "/payment/cancel",
          failureUrl: window.location.origin + "/payment/failure",
        },
      });
      if (res.priceUpdated) {
        setError(res.message || "Pricing has changed for this registration; please re-check the amount.");
        return;
      }
      if (!res.redirectUrl) { setError("Failed to generate link"); return; }
      setLinkResult({ redirectUrl: res.redirectUrl, amount: res.amount ?? amt, registrationId: linkRegistrationId });
      setInfo("Payment link generated");
    } catch (e: any) {
      setError(e?.message || "Failed to generate payment link");
    } finally {
      setLinkGenerating(false);
    }
  };

  const copyPaymentLink = async () => {
    if (!linkResult) return;
    try {
      await navigator.clipboard.writeText(linkResult.redirectUrl);
      setLinkCopied(true);
      setTimeout(() => setLinkCopied(false), 2000);
    } catch {
      setError("Could not copy link — copy it manually");
    }
  };

  const sendPaymentLink = async (channel: "email" | "whatsapp") => {
    if (!token || !linkResult) return;
    setError(null); setInfo(null);
    try {
      setLinkSending(channel);
      await apiFetch("/api/payments/yoco-checkout/send", {
        method: "POST",
        authToken: token,
        body: { registrationId: linkResult.registrationId, redirectUrl: linkResult.redirectUrl, channel },
      });
      setInfo(`Payment link sent via ${channel === "email" ? "email" : "WhatsApp"}`);
    } catch (e: any) {
      setError(e?.message || `Failed to send link via ${channel}`);
    } finally {
      setLinkSending(null);
    }
  };

  useEffect(() => {
    const regQ = search?.get("registrationId");
    if (regQ) setRegistrationId(regQ);
  }, [search]);

  // If a registration is preselected via query, infer and set its user
  useEffect(() => {
    if (!registrationId) return;
    const reg = registrations.find((r: any) => String(r.id) === String(registrationId));
    const uid = reg?.user?.id || reg?.userId;
    if (uid) setSelectedUserId(String(uid));
  }, [registrationId, registrations]);

  const [eventsIncludePast, setEventsIncludePast] = useState(false);
  const [eventsIncludeInactive, setEventsIncludeInactive] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        if (!token) return;
        const params = new URLSearchParams();
        if (eventsIncludePast) params.set("includePast", "true");
        if (eventsIncludeInactive) params.set("includeInactive", "true");
        const qs = params.toString() ? `?${params.toString()}` : "";
        const evs = await apiFetch<any[]>(`/api/events/all${qs}`, { authToken: token });
        setEvents(evs || []);
      } catch {}
    })();
  }, [token, eventsIncludePast, eventsIncludeInactive]);

  useEffect(() => {
    // Keep inputs sensible when toggling donation
    if (isDonation) {
      setRegistrationId("");
    } else {
      setEventId("");
    }
  }, [isDonation]);

  const createPayment = async () => {
    if (!token) return;
    setError(null); setInfo(null);
    const amt = parseFloat(amount || "0");
    if (!amt || amt <= 0) { setError("Enter a valid amount"); return; }
    if (!registrationId && !isDonation) {
      setError("Registration required unless this is a donation");
      return;
    }
    if (isDonation && !eventId && !registrationId) {
      setError("Select an event for donations");
      return;
    }
    try {
      setSubmitting(true);
      const res = await apiFetch<any>("/api/payments", {
        method: "POST",
        authToken: token,
        body: {
          amount: amt,
          method,
          userId: selectedUserId || undefined,
          registrationId: registrationId || undefined,
          eventId: eventId || undefined,
          isDonation: !!isDonation,
          paidAt: paidAtLocal ? new Date(paidAtLocal).toISOString() : undefined,
        }
      });
      setInfo(`Payment created (R ${amt.toFixed(2)})`);
      setAmount(""); setRegistrationId(""); setIsDonation(false); setEventId(""); setPaidAtLocal("");
      await Promise.all([loadPayments(), loadRegistrations()]);
    } catch (e: any) {
      setError(e?.message || "Failed to create payment");
    } finally {
      setSubmitting(false);
    }
  };

  // Stats
  const todayTotals = useMemo(() => {
    const start = new Date(); start.setHours(0,0,0,0);
    // Exclude donation-application legs — that money was already counted once, as the donation.
    const today = payments.filter(p => new Date(p.createdAt).getTime() >= start.getTime() && !isDonationLeg(p));
    const revenue = today.reduce((s,p)=> s + (p.amount||0), 0);
    const donations = today.filter(p => p.isDonation).length;
    return { revenue, donations, count: today.length };
  }, [payments]);

  const [mode, setMode] = useState<'payment'|'refund'|'donation'|'reconcile'|'link'>("payment");

  // Yoco unreconciled list and actions
  const [yocoLoading, setYocoLoading] = useState(false);
  const [yocoTxs, setYocoTxs] = useState<any[]>([]);
  const loadYocoUnreconciled = async () => {
    if (!token) return;
    try {
      setYocoLoading(true);
      const res = await apiFetch<any>("/api/yoco-transactions/unreconciled", { authToken: token });
      const items = Array.isArray(res?.data) ? res.data : (Array.isArray(res) ? res : []);
      setYocoTxs(items);
    } catch (e) {
      // ignore silently, shown on demand via error state if needed
    } finally {
      setYocoLoading(false);
    }
  };
  useEffect(() => { loadYocoUnreconciled(); }, [token]);

  // Inline reconcile form state per-row
  const [reconcileForm, setReconcileForm] = useState<{ txId: string | null; type: 'reg' | 'don' | null; userId: string; registrationId: string; eventId: string; submitting: boolean }>({ txId: null, type: null, userId: '', registrationId: '', eventId: '', submitting: false });

  const openReconcileToRegistration = (tx: any) => {
    const metaUser = tx?.raw?.payload?.metadata?.userId ? String(tx.raw.payload.metadata.userId) : '';
    setReconcileForm({ txId: tx.id, type: 'reg', userId: metaUser, registrationId: '', eventId: '', submitting: false });
  };

  const openReconcileAsDonation = (tx: any) => {
    const metaEvent = tx?.raw?.payload?.metadata?.eventId ? String(tx.raw.payload.metadata.eventId) : '';
    setReconcileForm({ txId: tx.id, type: 'don', userId: '', registrationId: '', eventId: metaEvent, submitting: false });
  };

  const regsForUser = (uid: string) => registrations
    .filter((r: any) => String(r.user?.id || r.userId) === String(uid))
    .filter((r: any) => {
      const out = regOutstanding[r.id]?.outstanding ?? 0;
      return out > 0.000001; // show only registrations with an outstanding balance
    });

  const submitReconcile = async () => {
    if (!token) return;
    const { txId, type, userId, registrationId, eventId } = reconcileForm;
    if (!txId || !type) return;
    try {
      setReconcileForm(prev => ({ ...prev, submitting: true }));
      if (type === 'reg') {
        if (!registrationId) { setError('Please select a registration'); return; }
        await apiFetch(`/api/yoco-transactions/${encodeURIComponent(txId)}/reconcile`, {
          method: 'POST',
          authToken: token,
          body: { registrationId }
        });
        setInfo('Reconciled to registration');
      } else {
        if (!eventId) { setError('Please select an event'); return; }
        await apiFetch(`/api/yoco-transactions/${encodeURIComponent(txId)}/reconcile`, {
          method: 'POST',
          authToken: token,
          body: { eventId, userId: userId || undefined }
        });
        setInfo('Reconciled as donation');
      }
      setReconcileForm({ txId: null, type: null, userId: '', registrationId: '', eventId: '', submitting: false });
      await Promise.all([loadYocoUnreconciled(), loadPayments(), loadRegistrations()]);
    } catch (e: any) {
      setError(e?.message || 'Failed to reconcile');
    } finally {
      setReconcileForm(prev => ({ ...prev, submitting: false }));
    }
  };

  const cancelReconcile = () => setReconcileForm({ txId: null, type: null, userId: '', registrationId: '', eventId: '', submitting: false });

  return (
    <div className="max-w-6xl mx-auto w-full p-4 sm:p-6 overflow-x-hidden">
      <div className="flex items-center justify-between mb-4 flex-wrap gap-3">
        <div className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-xl bg-brand-50 flex items-center justify-center shrink-0">
            <Wallet className="w-5 h-5 text-brand-600" />
          </div>
          <h1 className="text-2xl font-semibold text-gray-900">Payments</h1>
        </div>
        <button className="px-3 py-1.5 text-sm rounded-lg bg-gray-100 hover:bg-gray-200 text-gray-800 shadow-sm" onClick={() => router.push('/dashboard')}>Back</button>
      </div>

      {!canView && (
        <div className="p-3 border rounded bg-yellow-50 text-yellow-800 text-sm mb-4">
          You need supervisor or admin access to use this page.
        </div>
      )}

      {error && <div className="p-3 mb-3 border rounded bg-red-50 text-red-700 text-sm">{error}</div>}
      {info && <div className="p-3 mb-3 border rounded bg-emerald-50 text-emerald-800 text-sm">{info}</div>}

      <div className="mb-4 flex flex-wrap items-center gap-2">
        <label className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border ${mode === 'payment' ? 'bg-brand-600 text-white border-brand-600' : 'bg-white text-gray-800 border-gray-200'} cursor-pointer`}>
          <input type="radio" name="mode" value="payment" className="hidden" checked={mode==='payment'} onChange={() => setMode('payment')} />
          <Wallet className="w-4 h-4" />
          Payment
        </label>
        <label className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border ${mode === 'refund' ? 'bg-brand-600 text-white border-brand-600' : 'bg-white text-gray-800 border-gray-200'} cursor-pointer`}>
          <input type="radio" name="mode" value="refund" className="hidden" checked={mode==='refund'} onChange={() => setMode('refund')} />
          <RotateCcw className="w-4 h-4" />
          Refund
        </label>
        <label className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border ${mode === 'donation' ? 'bg-brand-600 text-white border-brand-600' : 'bg-white text-gray-800 border-gray-200'} cursor-pointer`}>
          <input type="radio" name="mode" value="donation" className="hidden" checked={mode==='donation'} onChange={() => setMode('donation')} />
          <HandHeart className="w-4 h-4" />
          Donations
        </label>
        <label className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border ${mode === 'reconcile' ? 'bg-brand-600 text-white border-brand-600' : 'bg-white text-gray-800 border-gray-200'} cursor-pointer`}>
          <input type="radio" name="mode" value="reconcile" className="hidden" checked={mode==='reconcile'} onChange={() => setMode('reconcile')} />
          <ScanLine className="w-4 h-4" />
          Reconcile
        </label>
        <label className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border ${mode === 'link' ? 'bg-brand-600 text-white border-brand-600' : 'bg-white text-gray-800 border-gray-200'} cursor-pointer`}>
          <input type="radio" name="mode" value="link" className="hidden" checked={mode==='link'} onChange={() => setMode('link')} />
          <Link2 className="w-4 h-4" />
          Payment Link
        </label>
      </div>


      <div className="grid lg:grid-cols-3 gap-6">
        <div className="lg:col-span-2 space-y-6">
          {/* Yoco reconciliation panel */}
          {mode === 'reconcile' && (
          <div className="border rounded-xl p-4 bg-white shadow-sm">
            <div className="flex items-center justify-between mb-3">
              <div className="text-lg font-semibold">Unreconciled Yoco Payments</div>
              <button className="px-3 py-1.5 text-sm rounded bg-gray-100 hover:bg-gray-200 text-gray-800" onClick={loadYocoUnreconciled} disabled={yocoLoading}>{yocoLoading ? 'Refreshing…' : 'Refresh'}</button>
            </div>
            {yocoTxs.length === 0 ? (
              <div className="text-sm text-gray-500">No unreconciled Yoco transactions.</div>
            ) : (
              <div className="overflow-x-auto w-full">
                <table className="min-w-full text-sm">
                  <thead>
                  <tr className="text-left border-b">
                    <th className="py-2 pr-3">Created</th>
                    <th className="py-2 pr-3">External ID</th>
                    <th className="py-2 pr-3">Amount</th>
                    <th className="py-2 pr-3">Checkout</th>
                    <th className="py-2 pr-3">Method</th>
                    <th className="py-2">Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                  {yocoTxs.map(tx => (
                    <React.Fragment key={tx.id}>
                      <tr className="border-b hover:bg-gray-50">
                        <td className="py-2 pr-3 whitespace-nowrap">{tx.createdDate ? new Date(tx.createdDate).toLocaleString() : '-'}</td>
                        <td className="py-2 pr-3 font-mono text-xs break-all max-w-[10rem]">{tx.externalId}</td>
                        <td className="py-2 pr-3">R {(Number(tx.amount || 0)/100).toFixed(2)}</td>
                        <td className="py-2 pr-3 break-all max-w-[8rem]">{tx.checkoutId || '-'}</td>
                        <td className="py-2 pr-3">{tx.methodType || '-'}</td>
                        <td className="py-2 flex flex-wrap gap-2">
                          <button className="px-2 py-1 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={() => openReconcileToRegistration(tx)}>To Registration</button>
                          <button className="px-2 py-1 rounded bg-emerald-600 text-white hover:bg-emerald-700" onClick={() => openReconcileAsDonation(tx)}>As Donation</button>
                          <button className="px-2 py-1 rounded bg-gray-600 text-white hover:bg-gray-700" onClick={async () => {
                            if (!token) return;
                            const ok = window.confirm('Ignore this transaction? It will be marked as ignored and reconciled.');
                            if (!ok) return;
                            try {
                              await apiFetch(`/api/yoco-transactions/${encodeURIComponent(tx.id)}/ignore`, { method: 'POST', authToken: token });
                              setInfo('Transaction ignored');
                              await loadYocoUnreconciled();
                            } catch (e:any) {
                              setError(e?.message || 'Failed to ignore');
                            }
                          }}>Ignore</button>
                        </td>
                      </tr>
                      {reconcileForm.txId === tx.id && (
                        <tr className="bg-gray-50">
                          <td colSpan={6} className="p-3">
                            {reconcileForm.type === 'reg' ? (
                              <div className="grid sm:grid-cols-3 gap-3 items-end">
                                <div>
                                  <label className="block text-xs text-gray-600 mb-1">User</label>
                                  <UserSearchField allUsers={allUsers} value={reconcileForm.userId} onChange={uid => setReconcileForm(prev => ({ ...prev, userId: uid, registrationId: '' }))} />
                                </div>
                                <div className="sm:col-span-2">
                                  <label className="block text-xs text-gray-600 mb-1">Registration</label>
                                  <select className="w-full border rounded px-3 py-2 text-sm" value={reconcileForm.registrationId} onChange={e => setReconcileForm(prev => ({ ...prev, registrationId: e.target.value }))} disabled={!reconcileForm.userId}>
                                    <option value="">Select registration…</option>
                                    {reconcileForm.userId && regsForUser(reconcileForm.userId).map((r: any) => {
                                      const out = regOutstanding[r.id]?.outstanding ?? 0;
                                      const label = `${r.event?.title || r.eventId || 'Event'} — Outstanding: R ${out.toFixed(2)} — #${String(r.id).slice(0,8)}`;
                                      return <option key={r.id} value={r.id} title={label}>{label}</option>;
                                    })}
                                    {reconcileForm.userId && regsForUser(reconcileForm.userId).length === 0 && (
                                      <option value="" disabled>No registrations with outstanding balance</option>
                                    )}
                                  </select>
                                </div>
                                <div className="flex gap-2">
                                  <button className="px-3 py-1.5 rounded bg-brand-600 text-white text-sm disabled:opacity-50" disabled={!reconcileForm.registrationId || reconcileForm.submitting} onClick={submitReconcile}>{reconcileForm.submitting ? 'Saving…' : 'Confirm'}</button>
                                  <button className="px-3 py-1.5 rounded bg-gray-200 text-gray-800 text-sm" onClick={cancelReconcile}>Cancel</button>
                                </div>
                              </div>
                            ) : (
                              <div className="grid sm:grid-cols-3 gap-3 items-end">
                                <div>
                                  <label className="block text-xs text-gray-600 mb-1">From (donor, optional)</label>
                                  <UserSearchField allUsers={allUsers} value={reconcileForm.userId} onChange={uid => setReconcileForm(prev => ({ ...prev, userId: uid }))} />
                                </div>
                                <div>
                                  <label className="block text-xs text-gray-600 mb-1">Event</label>
                                  <select className="w-full border rounded px-3 py-2 text-sm" value={reconcileForm.eventId} onChange={e => setReconcileForm(prev => ({ ...prev, eventId: e.target.value }))}>
                                    <option value="">Select event…</option>
                                    {events.map(ev => (<option key={ev.id} value={ev.id}>{ev.title}</option>))}
                                  </select>
                                </div>
                                <div className="flex gap-2">
                                  <button className="px-3 py-1.5 rounded bg-emerald-600 text-white text-sm disabled:opacity-50" disabled={!reconcileForm.eventId || reconcileForm.submitting} onClick={submitReconcile}>{reconcileForm.submitting ? 'Saving…' : 'Confirm'}</button>
                                  <button className="px-3 py-1.5 rounded bg-gray-200 text-gray-800 text-sm" onClick={cancelReconcile}>Cancel</button>
                                </div>
                              </div>
                            )}
                          </td>
                        </tr>
                      )}
                    </React.Fragment>
                  ))}
                  </tbody>
                </table>
              </div>
            )}
          </div>
          )}
          {mode === 'payment' && (
          <div className="border rounded-xl p-4 bg-white shadow-sm">
            <div className="text-lg font-semibold mb-3">Create payment</div>
            <div className="grid sm:grid-cols-2 gap-3">
              <div>
                <label className="block text-xs text-gray-600 mb-1">Amount</label>
                <input className="w-full border rounded px-3 py-2 text-sm" type="number" step="0.01" value={amount} onChange={e => setAmount(e.target.value)} />
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Method</label>
                <select className="w-full border rounded px-3 py-2 text-sm" value={method} onChange={e => setMethod(e.target.value)}>
                  <option value="cash">Cash</option>
                  <option value="card">Card</option>
                  <option value="eft">EFT</option>
                  <option value="voucher">Voucher</option>
                </select>
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">User</label>
                <UserSearchField allUsers={allUsers} value={selectedUserId} onChange={uid => { setSelectedUserId(uid); setRegistrationId(""); }} />
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Registration (required unless donation)</label>
                <select className="w-full max-w-full border rounded px-3 py-2 text-sm truncate" value={registrationId} onChange={e => setRegistrationId(e.target.value)} disabled={isDonation || !selectedUserId}>
                  <option value="">Select registration…</option>
                  <RegistrationOptions regs={regsForSelectedUser} regOutstanding={regOutstanding} />
                </select>
                {loadingRegs && <div className="text-xs text-gray-500 mt-1">Loading registrations…</div>}
              </div>
              <div className="flex items-center gap-2">
                <input id="isDonation" type="checkbox" checked={isDonation} onChange={e => setIsDonation(e.target.checked)} />
                <label htmlFor="isDonation" className="text-sm">Donation</label>
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Event (for donations)</label>
                <select className="w-full border rounded px-3 py-2 text-sm" value={eventId} onChange={e => setEventId(e.target.value)} disabled={!isDonation}>
                  <option value="">Select event…</option>
                  {events.map(ev => (<option key={ev.id} value={ev.id}>{ev.title}</option>))}
                </select>
                <div className="flex items-center gap-4 mt-1.5 text-xs text-gray-500">
                  <label className="flex items-center gap-1 cursor-pointer">
                    <input type="checkbox" checked={eventsIncludePast} onChange={e => setEventsIncludePast(e.target.checked)} />
                    Include past events
                  </label>
                  {user?.role === "admin" && (
                    <label className="flex items-center gap-1 cursor-pointer">
                      <input type="checkbox" checked={eventsIncludeInactive} onChange={e => setEventsIncludeInactive(e.target.checked)} />
                      Include inactive events
                    </label>
                  )}
                </div>
              </div>
              <div className="sm:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-3 mt-2">
                <div>
                  <label className="block text-xs text-gray-600 mb-1">Paid at (optional)</label>
                  <input className="w-full border rounded px-3 py-2 text-sm" type="datetime-local" value={paidAtLocal} onChange={e=>setPaidAtLocal(e.target.value)} max={new Date().toISOString().slice(0,16)} />
                  <div className="text-[10px] text-gray-500 mt-1">Leave blank to use current time</div>
                </div>
              </div>
            </div>
            <div className="mt-3">
              <button disabled={submitting} onClick={createPayment} className="mt-2 w-full px-3 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50 shadow-sm">{submitting ? 'Creating…' : 'Create payment'}</button>
            </div>
          </div>
          )}

          {mode === 'refund' && (
            <>
              <RefundSection
                payments={payments}
                allUsers={allUsers}
                usersList={usersList}
                regsForUser={(uid:string)=> registrations.filter((r:any)=> String(r.user?.id||r.userId)===String(uid))}
                regOutstanding={regOutstanding}
                onDone={async()=>{ await Promise.all([loadPayments(), loadRegistrations()]); setInfo('Refund recorded'); }}
              />
              {loadingUsers && <div className="text-xs text-gray-500">Loading users…</div>}
            </>
          )}

          {mode === 'donation' && (
            <>
              <DonationAssignSection
                payments={payments}
                allUsers={allUsers}
                registrations={registrations}
                regOutstanding={regOutstanding}
                onDone={async()=>{ await Promise.all([loadPayments(), loadRegistrations()]); setInfo('Donation assigned to registration'); }}
              />
              {loadingUsers && <div className="text-xs text-gray-500">Loading users…</div>}
              <AssignedDonationsList
                payments={payments}
                onDone={async()=>{ await Promise.all([loadPayments(), loadRegistrations()]); setInfo('Donation unassigned'); }}
              />
            </>
          )}

          {mode === 'link' && (
          <div className="border rounded-xl p-4 bg-white shadow-sm">
            <div className="text-lg font-semibold mb-3">Create payment link</div>
            <div className="grid sm:grid-cols-2 gap-3">
              <div>
                <label className="block text-xs text-gray-600 mb-1">User</label>
                <UserSearchField allUsers={allUsers} value={linkUserId} onChange={uid => { setLinkUserId(uid); setLinkRegistrationId(""); setLinkResult(null); }} />
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Registration</label>
                <select className="w-full max-w-full border rounded px-3 py-2 text-sm truncate" value={linkRegistrationId} onChange={e => setLinkRegistrationId(e.target.value)} disabled={!linkUserId}>
                  <option value="">Select registration…</option>
                  <RegistrationOptions regs={regsForLinkUser} regOutstanding={regOutstanding} />
                </select>
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Amount</label>
                <input className="w-full border rounded px-3 py-2 text-sm" type="number" step="0.01" value={linkAmount} onChange={e => setLinkAmount(e.target.value)} disabled={!linkRegistrationId} />
                <div className="text-[10px] text-gray-500 mt-1">Defaults to the outstanding balance; minimum R15 for a partial payment.</div>
              </div>
            </div>
            <div className="mt-3">
              <button disabled={linkGenerating || !linkRegistrationId} onClick={generatePaymentLink} className="w-full px-3 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50 shadow-sm">
                {linkGenerating ? 'Generating…' : 'Generate link'}
              </button>
            </div>
            {linkResult && (
              <div className="mt-4 p-3 border rounded-lg bg-gray-50 space-y-2">
                <div className="text-xs text-gray-600">Link for R {linkResult.amount.toFixed(2)}:</div>
                <div className="text-sm break-all font-mono bg-white border rounded px-2 py-1.5">{linkResult.redirectUrl}</div>
                <div className="flex flex-wrap gap-2 pt-1">
                  <button onClick={copyPaymentLink} className="px-3 py-1.5 text-xs rounded bg-gray-200 hover:bg-gray-300 text-gray-800">{linkCopied ? 'Copied!' : 'Copy link'}</button>
                  <button disabled={linkSending==='email'} onClick={() => sendPaymentLink('email')} className="px-3 py-1.5 text-xs rounded bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50">{linkSending==='email' ? 'Sending…' : 'Send via Email'}</button>
                  <button disabled={linkSending==='whatsapp'} onClick={() => sendPaymentLink('whatsapp')} className="px-3 py-1.5 text-xs rounded bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50">{linkSending==='whatsapp' ? 'Sending…' : 'Send via WhatsApp'}</button>
                </div>
                <div className="text-[10px] text-amber-700">Generating a new link for this registration will replace this one — the old link will no longer be honored.</div>
              </div>
            )}
          </div>
          )}

          <div className="border rounded-xl p-4 bg-white shadow-sm">
            <div className="flex items-center justify-between mb-2">
              <div className="text-lg font-semibold">Recent payments</div>
              {loadingList && <span className="text-xs text-gray-500">Loading…</span>}
            </div>
            <ul className="text-sm space-y-2 max-h-[520px] overflow-auto pr-2">
              {payments.filter(p => !isDonationLeg(p)).slice(0, 25).map(p => {
                const amt = p.amount || 0;
                const isRefund = amt < 0;
                return (
                  <li key={p.id} className={`border rounded p-2 ${isRefund ? 'bg-red-50' : ''}`}>
                    <div className="flex justify-between">
                      <div className={`font-medium ${isRefund ? 'text-red-700' : ''}`}>{isRefund ? '-' : ''}R {Math.abs(amt).toFixed(2)} {p.isDonation ? <span className="text-xs text-emerald-700">(donation)</span> : null} {isRefund ? <span className="text-xs text-red-700">(refund)</span> : null}</div>
                      <div className="text-xs text-gray-500">{new Date(p.createdAt).toLocaleString()}</div>
                    </div>
                    <div className="text-xs text-gray-600">Method: {p.method || 'payment'}</div>
                    {(p.registration?.user?.name || p.user?.name) && <div className="text-xs text-gray-600">Name: {p.registration?.user?.name || p.user?.name}</div>}
                    {p.registrationId && <div className="text-xs text-gray-600">Registration: #{String(p.registrationId).slice(0,8)}</div>}
                    {p.eventId && <div className="text-xs text-gray-600">Event: {p.event?.title || p.eventId}</div>}
                    {p.recordedBy?.name && String(p.recordedBy.id) !== String(p.userId) && (
                      <div className="text-xs text-gray-500">Recorded by: {p.recordedBy.name}</div>
                    )}
                  </li>
                );
              })}
              {payments.length === 0 && <li className="text-gray-500">No payments yet.</li>}
            </ul>
          </div>
        </div>

        <div className="space-y-6">
          <div className="border rounded-xl p-4 bg-white shadow-sm">
            <div className="text-lg font-semibold mb-3">Today</div>
            <div className="grid grid-cols-3 gap-2">
              <div className="border rounded p-3 bg-white">
                <div className="text-xs text-gray-500">Payments</div>
                <div className="text-lg font-semibold">{todayTotals.count}</div>
              </div>
              <div className="border rounded p-3 bg-white">
                <div className="text-xs text-gray-500">Revenue</div>
                <div className="text-lg font-semibold">R {todayTotals.revenue.toFixed(2)}</div>
              </div>
              <div className="border rounded p-3 bg-white">
                <div className="text-xs text-gray-500">Donations</div>
                <div className="text-lg font-semibold">{todayTotals.donations}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

type RefundSectionProps = {
  payments: any[];
  allUsers: any[];
  usersList: { id: string; name: string }[];
  regsForUser: (userId: string) => any[];
  regOutstanding: Record<string, { totalDue: number; totalPaid: number; outstanding: number }>;
  onDone: () => void | Promise<void>;
};

function RefundSection({ payments, allUsers, usersList, regsForUser, regOutstanding, onDone }: RefundSectionProps) {
  const { token } = useAuth();
  const [userId, setUserId] = useState<string>("");
  const [target, setTarget] = useState<'payment'|'registration'>('payment');
  const [paymentId, setPaymentId] = useState<string>("");
  const [registrationId, setRegistrationId] = useState<string>("");
  const [amount, setAmount] = useState<string>("");
  const [method, setMethod] = useState<string>("");
  const [reason, setReason] = useState<string>('');
  const [submitting, setSubmitting] = useState(false);
  const [err, setErr] = useState<string | null>(null);

  const paymentsForUser = useMemo(() => {
    if (!userId) return [] as any[];
    return payments.filter(p => String(p.userId) === String(userId) && (p.amount || 0) > 0);
  }, [payments, userId]);

  useEffect(() => {
    // Reset dependent fields on changes
    setPaymentId("");
    setRegistrationId("");
    setAmount("");
  }, [userId, target]);

  useEffect(() => {
    // If a payment selected, default amount and refund method to that payment's own —
    // still editable, but staff shouldn't have to remember to change it manually.
    if (target === 'payment') {
      const p = payments.find(pp => String(pp.id) === String(paymentId));
      if (p) {
        setAmount(String(Math.abs(p.amount || 0)));
        setMethod(refundMethodForOriginal(p.method));
      }
    }
  }, [paymentId, target, payments]);

  const regs = useMemo(() => regsForUser(userId), [regsForUser, userId]);

  const submitRefund = async () => {
    if (!token) return;
    setErr(null);
    const amt = parseFloat(amount || '0');
    if (!(amt > 0)) { setErr('Enter a valid refund amount'); return; }
    if (!userId) { setErr('Select a user'); return; }
    if (target === 'payment' && !paymentId) { setErr('Select a payment to refund'); return; }
    if (target === 'registration' && !registrationId) { setErr('Select a registration to refund against'); return; }
    if (!method) { setErr('Select a refund method'); return; }
    try {
      setSubmitting(true);
      await apiFetch('/api/payments/refund', {
        method: 'POST',
        authToken: token!,
        body: {
          userId,
          amount: amt,
          method,
          paymentId: target === 'payment' ? paymentId : undefined,
          registrationId: target === 'registration' ? registrationId : undefined,
          reason: reason || undefined
        }
      });
      setUserId(""); setPaymentId(""); setRegistrationId(""); setAmount(""); setReason(""); setMethod("");
      await onDone();
    } catch (e: any) {
      setErr(e?.message || 'Failed to create refund');
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div className="border rounded-xl p-4 bg-white shadow-sm">
      <div className="text-lg font-semibold mb-3">Refund</div>
      {err && <div className="p-2 mb-2 text-xs bg-red-50 text-red-700 border rounded">{err}</div>}
      <div className="grid gap-2">
        <label className="text-xs text-gray-600">User</label>
        <UserSearchField allUsers={allUsers} value={userId} onChange={uid => setUserId(uid)} />
        <label className="text-xs text-gray-600 mt-2">Refund target</label>
        <div className="flex gap-4 text-sm">
          <label className="flex items-center gap-2"><input type="radio" name="refundTarget" checked={target==='payment'} onChange={()=>setTarget('payment')} /> Payment</label>
          <label className="flex items-center gap-2"><input type="radio" name="refundTarget" checked={target==='registration'} onChange={()=>setTarget('registration')} /> Registration</label>
        </div>
        {target === 'payment' ? (
          <>
            <label className="text-xs text-gray-600 mt-2">Payment</label>
            <select className="w-full max-w-full border rounded px-3 py-2 text-sm truncate" value={paymentId} onChange={e => setPaymentId(e.target.value)} disabled={!userId}>
              <option value="">Select payment…</option>
              {paymentsForUser.map((p:any)=>{
                const label = `R ${(p.amount||0).toFixed(2)} — ${p.registrationId ? `Registration ${String(p.registrationId).slice(0,8)}` : (p.event?.title || p.eventId || 'Event')} — #${String(p.id).slice(0,8)}`;
                return <option key={p.id} value={p.id} title={label}>{label}</option>;
              })}
            </select>
          </>
        ) : (
          <>
            <label className="text-xs text-gray-600 mt-2">Registration</label>
            <select className="w-full max-w-full border rounded px-3 py-2 text-sm truncate" value={registrationId} onChange={e => setRegistrationId(e.target.value)} disabled={!userId}>
              <option value="">Select registration…</option>
              {regs.map((r:any)=>{
                const out = regOutstanding[r.id]?.outstanding ?? 0;
                const label = `${r.event?.title || r.eventId || 'Event'} — Outstanding: R ${out.toFixed(2)} — #${String(r.id).slice(0,8)}`;
                return <option key={r.id} value={r.id} title={label}>{label}</option>;
              })}
            </select>
          </>
        )}
        <div className="grid sm:grid-cols-2 gap-2 mt-2">
          <div>
            <label className="block text-xs text-gray-600 mb-1">Amount</label>
            <input type="number" step="0.01" className="w-full border rounded px-3 py-2 text-sm" value={amount} onChange={e => setAmount(e.target.value)} />
          </div>
          <div>
            <label className="block text-xs text-gray-600 mb-1">Method</label>
            <select className="w-full border rounded px-3 py-2 text-sm" value={method} onChange={e => setMethod(e.target.value)}>
              <option value="">Select method…</option>
              <option value="cash-refund">Cash refund</option>
              <option value="card-refund">Card refund</option>
              <option value="eft-refund">EFT refund</option>
              <option value="voucher-refund">Voucher refund</option>
            </select>
            <div className="text-[10px] text-gray-500 mt-1">Mirrors the method being refunded — this is what nets against that method's total in reports.</div>
          </div>
        </div>
        <label className="block text-xs text-gray-600 mt-2">Reason (optional)</label>
        <input type="text" className="w-full border rounded px-3 py-2 text-sm" value={reason} onChange={e => setReason(e.target.value)} placeholder="Reason or note…" />
        <button disabled={submitting} onClick={submitRefund} className="mt-2 px-3 py-1.5 text-sm rounded bg-rose-600 text-white hover:bg-rose-700 disabled:opacity-50 shadow-sm">{submitting ? 'Recording…' : 'Record refund'}</button>
      </div>
    </div>
  );
}

// Lists every donation-assignment leg (isDonationLeg) across all donations, with an Unassign
// action per row. Legs aren't shown anywhere else in the UI — "Recent payments" and the "Today"
// stats both deliberately filter them out (they're not new money, see isDonationLeg's comment) —
// so this is the only place staff can see and reverse an assignment.
type AssignedDonationsListProps = {
  payments: any[];
  onDone: () => void | Promise<void>;
};

function AssignedDonationsList({ payments, onDone }: AssignedDonationsListProps) {
  const { token } = useAuth();
  const [unassigningId, setUnassigningId] = useState<string | null>(null);
  const [err, setErr] = useDismissingState<string | null>(null);
  const [query, setQuery] = useState("");
  const [eventFilter, setEventFilter] = useState("");

  const allLegs = useMemo(() => {
    return payments
      .filter(isDonationLeg)
      .sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
  }, [payments]);

  const donationById = useMemo(() => {
    const m = new Map<string, any>();
    payments.forEach((p: any) => { if (p.isDonation) m.set(p.id, p); });
    return m;
  }, [payments]);

  // Events with at least one assigned leg — built from the legs themselves so the dropdown
  // never shows an event with nothing to filter down to.
  const eventOptions = useMemo(() => {
    const m = new Map<string, string>();
    allLegs.forEach((leg: any) => {
      const ev = leg.registration?.event;
      if (ev?.id) m.set(String(ev.id), ev.title || String(ev.id));
    });
    return Array.from(m.entries()).sort((a, b) => a[1].localeCompare(b[1], undefined, { sensitivity: 'base' }));
  }, [allLegs]);

  const legs = useMemo(() => {
    const q = query.trim().toLowerCase();
    return allLegs.filter((leg: any) => {
      if (eventFilter && String(leg.registration?.eventId || leg.registration?.event?.id) !== eventFilter) return false;
      if (!q) return true;
      const donation = donationById.get(leg.originalPaymentId);
      const haystack = [
        leg.registration?.user?.name,
        leg.registration?.user?.email,
        leg.registration?.event?.title,
        donation?.user?.name,
        donation?.user?.email,
        leg.registrationId,
        leg.originalPaymentId,
      ].filter(Boolean).join(' ').toLowerCase();
      return haystack.includes(q);
    });
  }, [allLegs, donationById, query, eventFilter]);

  const unassign = async (leg: any) => {
    if (!token) return;
    setErr(null);
    const ok = window.confirm("Unassign this donation? The registration's balance will increase, and any tickets issued only because this payment completed it may be revoked.");
    if (!ok) return;
    try {
      setUnassigningId(leg.id);
      await apiFetch('/api/payments/unassign-donation', {
        method: 'POST',
        authToken: token,
        body: { legId: leg.id }
      });
      await onDone();
    } catch (e: any) {
      setErr(e?.message || 'Failed to unassign donation');
    } finally {
      setUnassigningId(null);
    }
  };

  return (
    <div className="border rounded-xl p-4 bg-white shadow-sm">
      <div className="text-lg font-semibold mb-3">Assigned donations</div>
      {err && <div className="p-2 mb-2 text-xs bg-red-50 text-red-700 border rounded">{err}</div>}
      {allLegs.length > 0 && (
        <div className="flex flex-col sm:flex-row gap-2 mb-3">
          <input
            className="flex-1 border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400"
            placeholder="Search by registrant, donor, event…"
            value={query}
            onChange={e => setQuery(e.target.value)}
          />
          <select
            className="w-full sm:w-56 border rounded px-3 py-2 text-sm shrink-0"
            value={eventFilter}
            onChange={e => setEventFilter(e.target.value)}
          >
            <option value="">All events</option>
            {eventOptions.map(([id, title]) => (<option key={id} value={id}>{title}</option>))}
          </select>
        </div>
      )}
      {allLegs.length === 0 ? (
        <div className="text-sm text-gray-500">No donations have been assigned to registrations yet.</div>
      ) : legs.length === 0 ? (
        <div className="text-sm text-gray-500">No assigned donations match your search.</div>
      ) : (
        <ul className="text-sm space-y-2 max-h-[420px] overflow-auto pr-2">
          {legs.map((leg: any) => {
            const donation = donationById.get(leg.originalPaymentId);
            return (
              <li key={leg.id} className="border rounded p-2">
                <div className="flex justify-between items-start gap-2">
                  <div>
                    <div className="font-medium">R {(leg.amount || 0).toFixed(2)} — {leg.registration?.user?.name || leg.registration?.userId || 'Registrant'}</div>
                    <div className="text-xs text-gray-600">
                      Registration: #{String(leg.registrationId).slice(0,8)}{leg.registration?.event?.title ? ` — ${leg.registration.event.title}` : ''}
                    </div>
                    <div className="text-xs text-gray-500">
                      From donation by {donation?.user?.name || donation?.userId || 'Donor'} — #{String(leg.originalPaymentId).slice(0,8)}
                    </div>
                    <div className="text-xs text-gray-400">{new Date(leg.createdAt).toLocaleString()}</div>
                  </div>
                  <button
                    disabled={unassigningId === leg.id}
                    onClick={() => unassign(leg)}
                    className="px-2 py-1 text-xs rounded bg-rose-600 text-white hover:bg-rose-700 disabled:opacity-50 shrink-0"
                  >
                    {unassigningId === leg.id ? 'Unassigning…' : 'Unassign'}
                  </button>
                </div>
              </li>
            );
          })}
        </ul>
      )}
    </div>
  );
}

type DonationAssignSectionProps = {
  payments: any[];
  allUsers: any[];
  registrations: any[];
  regOutstanding: Record<string, { totalDue: number; totalPaid: number; outstanding: number }>;
  onDone: () => void | Promise<void>;
};

// Select a user, then one of their registrations, then one of the unassigned donations for
// that registration's event. The backend rejects the assignment if either the donation's own
// event or the target registration's event is closed (cashed up) — this UI filters both out
// proactively so staff aren't offered a choice that will just be rejected on submit.
function DonationAssignSection({ payments, allUsers, registrations, regOutstanding, onDone }: DonationAssignSectionProps) {
  const { token } = useAuth();
  const [userId, setUserId] = useState<string>("");
  const [registrationId, setRegistrationId] = useState<string>("");
  const [paymentId, setPaymentId] = useState<string>("");
  const [amountStr, setAmountStr] = useState<string>("");
  const [assigning, setAssigning] = useState(false);
  const [err, setErr] = useState<string | null>(null);

  useEffect(() => { setRegistrationId(""); setPaymentId(""); }, [userId]);
  useEffect(() => { setPaymentId(""); }, [registrationId]);

  // Registrations for the selected user that can actually receive a donation: an outstanding
  // balance to apply it to, and an event that isn't closed.
  const regsForUser = useMemo(() => {
    if (!userId) return [] as any[];
    return registrations.filter((r: any) => {
      if (String(r.user?.id || r.userId) !== String(userId)) return false;
      if (r.status === 'cancelled') return false;
      if (r.event?.cashupStatus === 'closed') return false;
      const out = regOutstanding[r.id]?.outstanding ?? 0;
      return out > 0.000001;
    });
  }, [registrations, regOutstanding, userId]);

  const selectedRegistration = useMemo(
    () => registrations.find((r: any) => String(r.id) === String(registrationId)),
    [registrations, registrationId]
  );

  // A donation is never mutated once assigned — each assignment creates a separate "leg"
  // Payment row (isDonation:false, originalPaymentId -> the donation). A donation's remaining
  // balance is its original amount minus every leg that references it, so it stays offerable
  // (and its registrationId stays null forever) until fully used up. A refund of the donation
  // itself also creates such a leg, with a negative amount — Math.abs() so a refund reduces the
  // remaining balance instead of inflating it (a raw signed sum would subtract a negative,
  // adding the refund back on top of what's left to allocate).
  const legsById = useMemo(() => {
    const m = new Map<string, number>();
    payments.forEach((p: any) => {
      if (p.originalPaymentId && !p.isDonation) {
        m.set(p.originalPaymentId, (m.get(p.originalPaymentId) || 0) + Math.abs(p.amount || 0));
      }
    });
    return m;
  }, [payments]);

  // Donations "relevant to that event" — donations logged against the same event as the
  // chosen registration that still have a remaining, unused balance.
  const donationsForEvent = useMemo(() => {
    if (!selectedRegistration) return [] as any[];
    const eventId = selectedRegistration.eventId || selectedRegistration.event?.id;
    return payments.filter((p: any) => {
      if (!p.isDonation || p.registrationId) return false;
      if (String(p.eventId) !== String(eventId)) return false;
      const remaining = (p.amount || 0) - (legsById.get(p.id) || 0);
      return remaining > 0.000001;
    });
  }, [payments, selectedRegistration, legsById]);

  const selectedDonation = useMemo(
    () => payments.find((p: any) => String(p.id) === String(paymentId)),
    [payments, paymentId]
  );

  const donationRemaining = selectedDonation
    ? (selectedDonation.amount || 0) - (legsById.get(selectedDonation.id) || 0)
    : 0;

  const outstanding = registrationId ? (regOutstanding[registrationId]?.outstanding ?? 0) : 0;

  // The most that can be allocated: never more than the donation's remaining balance, never
  // more than what's actually owed. Staff can type a smaller amount to leave a balance owing.
  const maxAllocatable = useMemo(() => {
    if (!selectedDonation) return 0;
    return Math.min(donationRemaining, outstanding);
  }, [selectedDonation, donationRemaining, outstanding]);

  // Default to "apply as much as needed" whenever a new donation is picked — the common
  // case needs no typing, but the field stays editable for a deliberate partial allocation.
  useEffect(() => {
    setAmountStr(maxAllocatable > 0 ? maxAllocatable.toFixed(2) : "");
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [paymentId]);

  const amountNum = parseFloat(amountStr || "0");
  const leftover = selectedDonation ? Math.max(0, donationRemaining - amountNum) : 0;

  const assign = async () => {
    if (!token) return;
    setErr(null);
    if (!paymentId || !registrationId) { setErr('Select a donation and a registration'); return; }
    if (!(amountNum > 0)) { setErr('Enter a valid amount to allocate'); return; }
    if (amountNum > maxAllocatable + 0.000001) { setErr(`Cannot allocate more than R ${maxAllocatable.toFixed(2)}`); return; }
    try {
      setAssigning(true);
      await apiFetch('/api/payments/assign-donation', {
        method: 'PUT',
        authToken: token,
        body: { paymentId, registrationId, amount: amountNum }
      });
      setUserId(""); setRegistrationId(""); setPaymentId(""); setAmountStr("");
      await onDone();
    } catch (e: any) {
      setErr(e?.message || 'Failed to assign donation');
    } finally {
      setAssigning(false);
    }
  };

  return (
    <div className="border rounded-xl p-4 bg-white shadow-sm">
      <div className="text-lg font-semibold mb-3">Assign donation to a registration</div>
      {err && <div className="p-2 mb-2 text-xs bg-red-50 text-red-700 border rounded">{err}</div>}
      <div className="grid gap-2">
        <label className="text-xs text-gray-600">1. User</label>
        <UserSearchField allUsers={allUsers} value={userId} onChange={setUserId} />

        <label className="text-xs text-gray-600 mt-2">2. Registration</label>
        <select
          className="w-full max-w-full border rounded px-3 py-2 text-sm truncate"
          value={registrationId}
          onChange={e => setRegistrationId(e.target.value)}
          disabled={!userId}
        >
          <option value="">Select registration…</option>
          <RegistrationOptions regs={regsForUser} regOutstanding={regOutstanding} />
        </select>
        {userId && regsForUser.length === 0 && (
          <div className="text-xs text-gray-500">No eligible registrations for this user — either nothing outstanding, or the event is closed.</div>
        )}

        <label className="text-xs text-gray-600 mt-2">3. Donation</label>
        <select
          className="w-full max-w-full border rounded px-3 py-2 text-sm truncate"
          value={paymentId}
          onChange={e => setPaymentId(e.target.value)}
          disabled={!registrationId}
        >
          <option value="">Select donation…</option>
          {donationsForEvent.map((p: any) => {
            const remaining = (p.amount || 0) - (legsById.get(p.id) || 0);
            const label = `R ${remaining.toFixed(2)} of R ${(p.amount || 0).toFixed(2)} left — ${p.user?.name || p.userId || 'Donor'} — #${String(p.id).slice(0,8)}`;
            return <option key={p.id} value={p.id} title={label}>{label}</option>;
          })}
        </select>
        {registrationId && donationsForEvent.length === 0 && (
          <div className="text-xs text-gray-500">No donations with a remaining balance for this event.</div>
        )}

        {selectedDonation && (
          <>
            <label className="text-xs text-gray-600 mt-2">4. Amount to allocate</label>
            <input
              type="number"
              step="0.01"
              min="0.01"
              max={maxAllocatable}
              className="w-full border rounded px-3 py-2 text-sm"
              value={amountStr}
              onChange={e => setAmountStr(e.target.value)}
            />
            <div className="text-xs text-gray-500">
              Donation has R {donationRemaining.toFixed(2)} remaining (of R {(selectedDonation.amount || 0).toFixed(2)} total); outstanding balance is R {outstanding.toFixed(2)}.
              {leftover > 0.000001 && <> The remaining R {leftover.toFixed(2)} will stay available on this donation for future assignments.</>}
            </div>
          </>
        )}

        <button
          disabled={!paymentId || assigning || !(amountNum > 0)}
          onClick={assign}
          className="mt-2 px-3 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50 shadow-sm"
        >
          {assigning ? 'Assigning…' : 'Assign donation'}
        </button>
      </div>
    </div>
  );
}

export default function PaymentsPage() {
  return (
    <Suspense fallback={<div className="p-6">Loading...</div>}>
      <PaymentsContent />
    </Suspense>
  );
}
