import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
    Sheet,
    SheetContent,
    SheetDescription,
    SheetFooter,
    SheetHeader,
    SheetTitle,
} from '@/components/ui/sheet';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import SearchableSelect from '@/components/ui/select-search';
import { useTranslation } from '@/hooks/use-translation';
import { router } from '@inertiajs/react';
import axios from 'axios';
import { ArrowDownCircle, ArrowUpCircle, Building2, CalendarDays, CheckCircle2, FileText, Link2, Loader2, Search, Wallet } from 'lucide-react';
import * as React from 'react';
import { toast } from 'sonner';
import { FrDateInput } from './fr-date-input';
import { buildReservationSearchValue, frDate } from './reservation-search';

interface Props {
    open: boolean;
    onOpenChange: (v: boolean) => void;
    type: 'entree' | 'sortie';
}

interface PickerItem {
    id: number;
    name: string;
    property_name?: string;
    date_from?: string | null;
    date_to?: string | null;
    remaining_tnd?: number | null;
    paid_tnd?: number;
    total_tnd?: number | null;
    // Extra text folded into SearchableSelect's filter haystack so the
    // reservation can be found by property name / dates, not just by the
    // "#contract — client" label.
    searchValue?: string;
}

interface AvailableReceipt {
    id: number;
    date: string;
    amount: number;
    currency: string;
    payment_mode: string;
    note: string | null;
    signature_status: 'signed' | 'unsigned' | string;
    already_on: string | null;
    giver_name: string | null;
    receiver_name: string | null;
}

const todayIso = () => new Date().toISOString().slice(0, 10);

export function EntryDialog({ open, onOpenChange, type }: Props) {
    const { t } = useTranslation();
    const [date, setDate] = React.useState<string>(todayIso());
    const [designation, setDesignation] = React.useState('');
    const [amount, setAmount] = React.useState<number>(0);
    const [submitting, setSubmitting] = React.useState(false);
    // Unified drawer: the type prop seeds the initial kind; the segmented
    // control lets the user switch encaissement / décaissement in place.
    const [kind, setKind] = React.useState<'entree' | 'sortie'>(type);

    // Optional link to a source record (Recovery for entrée, Expense for sortie)
    const [linked, setLinked] = React.useState(false);
    const [linkedId, setLinkedId] = React.useState<string>('');
    const [items, setItems] = React.useState<PickerItem[]>([]);
    const [loadingItems, setLoadingItems] = React.useState(false);
    // Reservation date-range filter (Encaisser) — narrows the picker list by
    // check-in date so a long reservation list is easy to scan.
    const [resvFrom, setResvFrom] = React.useState('');
    const [resvTo, setResvTo] = React.useState('');

    // Once a reservation is picked, fetch the receipts available to attach.
    const [receipts, setReceipts] = React.useState<AvailableReceipt[]>([]);
    const [loadingReceipts, setLoadingReceipts] = React.useState(false);
    const [pickedReceiptId, setPickedReceiptId] = React.useState<number | null>(null);
    /**
     * Receipt mode (only relevant when not picking an existing receipt):
     *   - 'none'             → no receipt; free recovery / sortie
     *   - 'create_unsigned'  → create a receipt + link it (cash recorded via Recovery/Expense; signature can be added later)
     *   - 'create_signed'    → create a receipt + redirect to signing flow (full proof of payment)
     */
    // 'none' is only used internally when an EXISTING receipt is picked.
    // There is no "free encaissement" — a receipt is always involved.
    type ReceiptMode = 'none' | 'create_unsigned' | 'create_signed';
    const [receiptMode, setReceiptMode] = React.useState<ReceiptMode>('create_unsigned');
    const [receiptNote, setReceiptNote] = React.useState('');
    const createReceiptMode = receiptMode !== 'none';
    // Facture (invoice/receipt scan) file uploaded with a Décaisser entry.
    // Only used when sortie + property is picked (Expense path).
    const [factureFile, setFactureFile] = React.useState<File | null>(null);

    // ---- Team allocation (Décaisser only) ------------------------------
    // When the finance / admin user wants to hand cash to a team member.
    // Creates paired ledger entries (sortie on giver, entrée on receiver)
    // plus a pending receipt for the receiver to sign.
    const [allocateMode, setAllocateMode] = React.useState(false);
    const [teamMembers, setTeamMembers] = React.useState<{ id: number; name: string }[]>([]);
    const [pickedTeamMemberId, setPickedTeamMemberId] = React.useState<string>('');

    // ---- Fournisseur / diverse (Décaisser only) ------------------------
    // For cash spent on an outside vendor not in the team. Creates an
    // Expense with housing_id = null; the facture image is the proof.
    const [fournisseurMode, setFournisseurMode] = React.useState(false);
    const [fournisseurName, setFournisseurName] = React.useState('');

    // ---- Reclamations (Décaisser, property-linked) ---------------------
    // When the user is recording a property expense, they can pick an open
    // reclamation that's tied to them (team member) or any open one (admin).
    // Picking auto-fills the designation + the property and marks the
    // reclamation as paid via expense_id on submit.
    interface ReclamationItem {
        id: number; property_id: number; property: string; title: string;
        commentaire: string | null; priority?: string | null;
        assigned_user_id?: number | null; assigned_user_name?: string | null;
    }
    const [reclamations, setReclamations] = React.useState<ReclamationItem[]>([]);
    const [pickedReclamationId, setPickedReclamationId] = React.useState<string>('');
    // Allocation against a réclamation — receiver auto-resolves to the assignee.
    const [allocReclamations, setAllocReclamations] = React.useState<ReclamationItem[]>([]);
    const [allocReclamationId, setAllocReclamationId] = React.useState<string>('');

    const isEntree = kind === 'entree';
    const title = isEntree
        ? t('fond_de_caisse', 'entry_dialog_title_in')
        : t('fond_de_caisse', 'entry_dialog_title_out');
    const description = isEntree
        ? t('fond_de_caisse', 'entry_dialog_desc_in')
        : t('fond_de_caisse', 'entry_dialog_desc_out');

    const linkLabel = isEntree
        ? t('fond_de_caisse', 'link_to_reservation')
        : t('fond_de_caisse', 'link_to_property');
    const linkPickerPlaceholder = isEntree
        ? t('fond_de_caisse', 'link_pick_reservation')
        : t('fond_de_caisse', 'link_pick_property');

    const designationRef = React.useRef<HTMLInputElement>(null);

    // Reset whenever the dialog reopens
    React.useEffect(() => {
        if (open) {
            setKind(type);
            setDate(todayIso());
            setDesignation('');
            setAmount(0);
            setLinked(false);
            setLinkedId('');
            setItems([]);
            setResvFrom('');
            setResvTo('');
            setReceipts([]);
            setPickedReceiptId(null);
            setReceiptMode('create_unsigned');
            setReceiptNote('');
            setFactureFile(null);
            setAllocateMode(false);
            setPickedTeamMemberId('');
            setFournisseurMode(false);
            setFournisseurName('');
            setReclamations([]);
            setPickedReclamationId('');
            setAllocReclamations([]);
            setAllocReclamationId('');
            setTimeout(() => designationRef.current?.focus(), 50);
        }
    }, [open]);

    // When a reservation gets picked (Encaisser only), fetch its available
    // receipts so the user can attach one instead of creating a free Recovery.
    React.useEffect(() => {
        if (!isEntree || !linked || !linkedId) {
            setReceipts([]);
            setPickedReceiptId(null);
            return;
        }
        let cancelled = false;
        setLoadingReceipts(true);
        axios
            .get<AvailableReceipt[]>(`/fond-de-caisse/reservations/${linkedId}/receipts`)
            .then((res) => {
                if (cancelled) return;
                setReceipts(res.data);
                // Pre-clear any previously-selected receipt — different
                // reservation = different receipt list.
                setPickedReceiptId(null);
                setReceiptMode('create_unsigned');
                setReceiptNote('');
            })
            .catch(() => {
                if (!cancelled) toast.error('Erreur lors du chargement des reçus');
            })
            .finally(() => {
                if (!cancelled) setLoadingReceipts(false);
            });
        return () => { cancelled = true; };
    }, [isEntree, linked, linkedId]);

    // Auto-fill the amount from the picked receipt (the receipt's amount is
    // the source of truth when it's signed).
    React.useEffect(() => {
        if (!pickedReceiptId) return;
        const r = receipts.find((rr) => rr.id === pickedReceiptId);
        if (r) {
            setAmount(r.amount);
            if (r.note && designation === '') setDesignation(r.note);
        }
    }, [pickedReceiptId, receipts]); // eslint-disable-line react-hooks/exhaustive-deps

    // Lazy-load team members + open réclamations the first time the user
    // toggles allocate mode.
    React.useEffect(() => {
        if (!allocateMode) return;
        if (teamMembers.length === 0) {
            axios.get<{ id: number; name: string }[]>('/fond-de-caisse/team-members')
                .then((res) => setTeamMembers(res.data))
                .catch(() => toast.error("Impossible de charger l'équipe"));
        }
        if (allocReclamations.length === 0) {
            axios.get<ReclamationItem[]>('/fond-de-caisse/reclamations')
                .then((res) => setAllocReclamations(res.data.filter((r) => r.assigned_user_id)))
                .catch(() => { /* silent — réclamation picker is optional */ });
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [allocateMode]);

    // When a réclamation is picked for allocation, the receiver is forced to
    // its assignee — clear any manually-picked team member.
    React.useEffect(() => {
        if (!allocReclamationId) return;
        const r = allocReclamations.find((x) => x.id.toString() === allocReclamationId);
        if (r?.assigned_user_id) {
            setPickedTeamMemberId(String(r.assigned_user_id));
            if (!designation.trim()) {
                const fill = [r.title, r.commentaire].filter(Boolean).join(' — ');
                if (fill) setDesignation(fill);
            }
        }
    }, [allocReclamationId, allocReclamations]); // eslint-disable-line react-hooks/exhaustive-deps

    // Load reclamations filtered by the selected property. Refetches every
    // time the property selection changes so the dropdown only ever shows
    // reclamations for the current apartment.
    React.useEffect(() => {
        if (isEntree || !linked) {
            setReclamations([]);
            setPickedReclamationId('');
            return;
        }
        const url = linkedId
            ? `/fond-de-caisse/reclamations?property_id=${linkedId}`
            : '/fond-de-caisse/reclamations';
        axios.get<ReclamationItem[]>(url)
            .then((res) => {
                setReclamations(res.data);
                // If the previously-picked reclamation isn't in the new
                // filtered set, clear it.
                if (pickedReclamationId && !res.data.find((r) => r.id.toString() === pickedReclamationId)) {
                    setPickedReclamationId('');
                }
            })
            .catch(() => { /* silent — picker is optional */ });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isEntree, linked, linkedId]);

    // When the user picks a reclamation, snap the property + designation to it.
    React.useEffect(() => {
        if (!pickedReclamationId) return;
        const r = reclamations.find((x) => x.id.toString() === pickedReclamationId);
        if (!r) return;
        setLinkedId(String(r.property_id));
        if (!designation.trim()) {
            const fill = [r.title, r.commentaire].filter(Boolean).join(' — ');
            if (fill) setDesignation(fill);
        }
    }, [pickedReclamationId, reclamations]); // eslint-disable-line react-hooks/exhaustive-deps

    // Lazy-load picker data the first time the user toggles "Linked"
    React.useEffect(() => {
        if (!linked || items.length > 0 || loadingItems) return;
        const url = isEntree
            ? '/fond-de-caisse/search/reservations'
            : '/fond-de-caisse/search/properties';
        setLoadingItems(true);
        axios
            .get<PickerItem[]>(url)
            .then((res) => {
                // Reservations are searched by property name / dates too, not
                // just the "#contract — client" label — see reservation-search.ts.
                const data = isEntree
                    ? res.data.map((it) => ({ ...it, searchValue: buildReservationSearchValue(it) }))
                    : res.data;
                setItems(data);
            })
            .catch(() => toast.error(t('fond_de_caisse', 'link_load_error')))
            .finally(() => setLoadingItems(false));
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [linked, isEntree]);

    const submit = () => {
        if (submitting) return;
        if (designation.trim().length === 0) {
            toast.error(t('fond_de_caisse', 'entry_validation_designation'));
            designationRef.current?.focus();
            return;
        }
        if (amount <= 0) {
            toast.error(t('fond_de_caisse', 'entry_validation_amount'));
            return;
        }
        if (linked && !linkedId) {
            toast.error(
                isEntree
                    ? t('fond_de_caisse', 'link_validation_reservation')
                    : t('fond_de_caisse', 'link_validation_property'),
            );
            return;
        }

        // Team allocation flow — Décaisser only. Hits a dedicated endpoint
        // that creates the paired ledger entries + the receipt to be signed.
        if (!isEntree && allocateMode) {
            if (!allocReclamationId && !pickedTeamMemberId) {
                toast.error('Choisissez une réclamation ou un membre de l\'équipe');
                return;
            }
            setSubmitting(true);
            router.post(route('fond-de-caisse.allocate'), {
                date,
                amount,
                designation: designation.trim(),
                receiver_user_id: pickedTeamMemberId ? Number(pickedTeamMemberId) : undefined,
                reclamation_id: allocReclamationId ? Number(allocReclamationId) : undefined,
            }, {
                preserveScroll: false,
                onSuccess: () => onOpenChange(false),
                onError: (errors) => {
                    // Inertia turns 422 validation errors into `errors`. For
                    // 500s our new exception handler returns JSON which
                    // Inertia surfaces under `errors.message`.
                    const first = (errors as Record<string, string>).message
                        ?? Object.values(errors as Record<string, string>)[0];
                    toast.error(typeof first === 'string' ? first : "Erreur lors de l'allocation");
                },
                onFinish: () => setSubmitting(false),
            });
            return;
        }

        // Block overpayment on encaissement — cash drawer must record what's
        // actually owed on the reservation. To over-pay, the user should
        // remove the reservation link and record a free entry, or split.
        if (isEntree && linked && linkedId) {
            const picked = items.find((it) => it.id.toString() === linkedId);
            const remaining = picked?.remaining_tnd ?? null;
            if (remaining !== null && amount > remaining + 0.009) {
                toast.error(
                    `Le montant (${amount.toLocaleString('fr-TN', { minimumFractionDigits: 2 })} TND) dépasse le reste à payer (${remaining.toLocaleString('fr-TN', { minimumFractionDigits: 2 })} TND).`,
                );
                return;
            }
        }

        const payload: Record<string, unknown> = {
            date,
            designation: designation.trim(),
            type: kind,
            amount,
            currency: 'TND',
            amount_tnd: amount,
        };
        if (linked && linkedId) {
            if (isEntree) payload.reservation_id = Number(linkedId);
            else payload.property_id = Number(linkedId);
        }
        if (isEntree && pickedReceiptId) {
            payload.receipt_id = pickedReceiptId;
        }
        if (createReceiptMode && !pickedReceiptId) {
            payload.create_receipt = true;
            payload.receipt_note = receiptNote.trim() || undefined;
            payload.sign_after_create = receiptMode === 'create_signed';
        }
        // Décaisser: attach the facture/justificatif file when present. Inertia
        // detects a File in the payload and switches to multipart automatically.
        if (!isEntree && factureFile) {
            payload.facture = factureFile;
        }
        // Décaisser: when a reclamation was picked, attach its id so the
        // backend can mark the reclamation as paid by linking expense_id.
        if (!isEntree && pickedReclamationId) {
            payload.reclamation_id = Number(pickedReclamationId);
        }
        // Décaisser fournisseur — name is mandatory; facture is OPTIONAL
        // (can be uploaded later from the row's edit dialog).
        if (!isEntree && fournisseurMode) {
            if (!fournisseurName.trim()) {
                toast.error('Saisissez le nom du fournisseur');
                return;
            }
            payload.fournisseur = fournisseurName.trim();
        }
        // Décaisser: hard guard — for sortie, one of the two modes MUST be picked.
        if (!isEntree && !allocateMode && !fournisseurMode) {
            toast.error('Choisissez un type de décaissement : équipier ou fournisseur');
            return;
        }

        // When we're CREATING a SIGNED receipt, the backend redirects to the
        // signing page — so we must NOT use a partial reload (which only
        // refreshes named props on the current page). For every other path,
        // partial reload is faster.
        const isFullVisit = createReceiptMode && receiptMode === 'create_signed' && !pickedReceiptId;

        setSubmitting(true);
        router.post(route('fond-de-caisse.store'), payload, {
            preserveScroll: !isFullVisit,
            ...(isFullVisit ? {} : { only: ['rows', 'summary', 'flash'] }),
            onSuccess: () => onOpenChange(false),
            onFinish: () => setSubmitting(false),
        });
    };

    const onKeyDown = (e: React.KeyboardEvent) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            submit();
        }
    };

    const Icon = isEntree ? ArrowUpCircle : ArrowDownCircle;
    const iconClass = isEntree ? 'text-emerald-600' : 'text-red-600';
    const buttonClass = isEntree
        ? 'bg-emerald-700 text-white hover:bg-emerald-800'
        : 'bg-red-700 text-white hover:bg-red-800';

    // Switch encaissement / décaissement in place, clearing the other type's
    // options so nothing leaks across (e.g. an entrée reservation link into a
    // sortie).
    const switchKind = (k: 'entree' | 'sortie') => {
        if (k === kind) return;
        setKind(k);
        setLinked(false);
        setLinkedId('');
        setPickedReceiptId(null);
        setAllocateMode(false);
        setPickedTeamMemberId('');
        setAllocReclamationId('');
        setFournisseurMode(false);
        setFournisseurName('');
        setPickedReclamationId('');
        setFactureFile(null);
    };

    // Overpayment detection: when the picked reservation has a known "reste à
    // payer" and the entered amount exceeds it. Lets us warn the user so they
    // don't accidentally over-pay a reservation. We still allow it — the user
    // can confirm — but make the consequence explicit.
    const pickedReservation = (isEntree && linked && linkedId)
        ? items.find((it) => it.id.toString() === linkedId)
        : undefined;
    const remainingForPicked = pickedReservation?.remaining_tnd ?? null;
    const overpayBy = (remainingForPicked !== null && amount > remainingForPicked)
        ? amount - remainingForPicked
        : 0;

    return (
        <Sheet open={open} onOpenChange={onOpenChange}>
            <SheetContent side="right" className="flex w-full flex-col gap-0 p-0 sm:max-w-xl">
                <SheetHeader className="border-b px-6 pt-6 pb-4">
                    <SheetTitle className="flex items-center gap-2">
                        <Icon className={`h-6 w-6 ${iconClass}`} />
                        {title}
                    </SheetTitle>
                    <SheetDescription>{description}</SheetDescription>
                </SheetHeader>

                <div className="flex min-h-0 flex-1 flex-col gap-4 overflow-x-hidden overflow-y-auto px-6 py-5">
                    {/* Type switch — one drawer handles both encaissement and décaissement. */}
                    <div className="border-input grid grid-cols-2 gap-1 rounded-lg border p-1">
                        <button
                            type="button"
                            onClick={() => switchKind('entree')}
                            className={`flex h-10 items-center justify-center gap-1.5 rounded-md text-sm font-medium transition ${
                                isEntree ? 'bg-emerald-50 text-emerald-700' : 'text-muted-foreground hover:bg-muted/60'
                            }`}
                        >
                            <ArrowUpCircle className="h-4 w-4" />
                            {t('fond_de_caisse', 'encaisser')}
                        </button>
                        <button
                            type="button"
                            onClick={() => switchKind('sortie')}
                            className={`flex h-10 items-center justify-center gap-1.5 rounded-md text-sm font-medium transition ${
                                !isEntree ? 'bg-red-50 text-red-700' : 'text-muted-foreground hover:bg-muted/60'
                            }`}
                        >
                            <ArrowDownCircle className="h-4 w-4" />
                            {t('fond_de_caisse', 'decaisser')}
                        </button>
                    </div>

                    {/* Hero amount — the focal field. */}
                    <div className={`rounded-xl p-4 text-center ${isEntree ? 'bg-emerald-50' : 'bg-red-50'}`}>
                        <Label htmlFor="entry-amount" className={`text-[11px] font-medium uppercase tracking-wide ${isEntree ? 'text-emerald-700' : 'text-red-700'}`}>
                            {isEntree ? 'Montant encaissé' : 'Montant décaissé'} (TND)
                        </Label>
                        <Input
                            id="entry-amount"
                            type="number"
                            step="0.01"
                            min="0"
                            value={amount || ''}
                            onChange={(e) => setAmount(parseFloat(e.target.value) || 0)}
                            onKeyDown={onKeyDown}
                            placeholder="0.00"
                            disabled={isEntree && pickedReceiptId !== null}
                            className={`mt-1 h-14 border-0 bg-transparent text-center text-3xl font-semibold tabular-nums shadow-none focus-visible:ring-0 ${isEntree ? 'text-emerald-700' : 'text-red-700'}`}
                        />
                        {overpayBy > 0 && remainingForPicked !== null && (
                            <div className="border-red-300 bg-red-100 text-red-900 mt-2 flex items-start justify-between gap-3 rounded-md border px-3 py-2 text-left text-xs">
                                <div className="flex items-start gap-2">
                                    <span className="mt-0.5">⛔</span>
                                    <div>
                                        <div className="font-medium">Montant supérieur au reste à payer</div>
                                        <div className="text-red-800/80">
                                            Reste à payer : {remainingForPicked.toLocaleString('fr-TN', { minimumFractionDigits: 2 })} TND —
                                            dépassement de {overpayBy.toLocaleString('fr-TN', { minimumFractionDigits: 2 })} TND.
                                            L'encaissement sera bloqué.
                                        </div>
                                    </div>
                                </div>
                                <button
                                    type="button"
                                    onClick={() => setAmount(remainingForPicked)}
                                    className="shrink-0 rounded-md bg-red-200 px-2 py-1 text-[11px] font-medium text-red-900 transition-colors hover:bg-red-300"
                                >
                                    Ajuster à {remainingForPicked.toLocaleString('fr-TN', { minimumFractionDigits: 2 })}
                                </button>
                            </div>
                        )}
                    </div>
                    <div className="grid gap-2">
                        <Label htmlFor="entry-date" className="text-sm">Date</Label>
                        <FrDateInput value={date} onChange={setDate} inputClassName="h-12 text-base" />
                    </div>

                    <div className="grid gap-2">
                        <Label htmlFor="entry-designation" className="text-sm">{t('fond_de_caisse', 'entry_field_designation')}</Label>
                        <Input
                            id="entry-designation"
                            ref={designationRef}
                            type="text"
                            value={designation}
                            onChange={(e) => setDesignation(e.target.value)}
                            onKeyDown={onKeyDown}
                            placeholder={t('fond_de_caisse', 'designation_placeholder')}
                            className="h-12 text-base"
                        />
                    </div>


                    {/* Team allocation (Décaisser only) — hand cash to a team
                        member. Creates paired ledger entries + a receipt to be
                        signed by the receiver. Mutually exclusive with property
                        linking — if you're handing cash, it's not a property
                        expense. */}
                    {!isEntree && (
                        <div className="bg-muted/40 border-input grid gap-3 rounded-lg border p-4">
                            <label className="flex cursor-pointer items-center gap-2 text-sm font-medium">
                                <Checkbox
                                    checked={allocateMode}
                                    onCheckedChange={(v) => {
                                        const next = v === true;
                                        setAllocateMode(next);
                                        if (next) setLinked(false); // mutually exclusive with property link
                                    }}
                                />
                                <Wallet className="text-muted-foreground h-4 w-4" />
                                <span>Allouer à un membre de l'équipe</span>
                            </label>
                            {allocateMode && (
                                <div className="grid gap-3">
                                    {/* Réclamation picker — when chosen, the receiver
                                        is auto-locked to the réclamation's assignee. */}
                                    {allocReclamations.length > 0 && (
                                        <div className="grid gap-1.5">
                                            <Label className="text-xs text-muted-foreground">
                                                Réclamation à financer (optionnel)
                                            </Label>
                                            <select
                                                value={allocReclamationId}
                                                onChange={(e) => {
                                                    setAllocReclamationId(e.target.value);
                                                    if (!e.target.value) setPickedTeamMemberId('');
                                                }}
                                                className="h-10 w-full rounded-md border bg-background px-2 text-sm"
                                            >
                                                <option value="">— Allocation libre (choisir un équipier) —</option>
                                                {allocReclamations.map((r) => (
                                                    <option key={r.id} value={r.id}>
                                                        #{r.id} · {r.property} · {r.title}
                                                        {r.assigned_user_name ? ` → ${r.assigned_user_name}` : ''}
                                                    </option>
                                                ))}
                                            </select>
                                        </div>
                                    )}

                                    {allocReclamationId ? (
                                        // Receiver is locked to the réclamation's assignee.
                                        (() => {
                                            const r = allocReclamations.find((x) => x.id.toString() === allocReclamationId);
                                            return (
                                                <div className="bg-background border-input rounded-md border px-3 py-2 text-sm">
                                                    <span className="text-muted-foreground text-xs">Receveur (responsable de la réclamation)</span>
                                                    <div className="font-medium">{r?.assigned_user_name ?? '—'}</div>
                                                </div>
                                            );
                                        })()
                                    ) : (
                                        <SearchableSelect
                                            value={pickedTeamMemberId}
                                            onValueChange={setPickedTeamMemberId}
                                            items={teamMembers}
                                            placeholder="Choisir un équipier…"
                                            allOptionLabel=""
                                            notFoundMessage="Aucun membre trouvé"
                                            className="bg-background text-foreground h-12 w-full justify-between rounded-md px-3 text-base font-normal"
                                        />
                                    )}
                                    <p className="text-muted-foreground text-xs">
                                        Un reçu CASH · TND sera créé. Faites signer
                                        le destinataire pour confirmer la remise.
                                        Le solde est débité de votre fond de caisse
                                        et crédité au sien.
                                    </p>
                                </div>
                            )}
                        </div>
                    )}

                    {/* Fournisseur / diverse (Décaisser only). For cash spent
                        on an outside vendor. The facture image is the proof —
                        no receipt or property needed. */}
                    {!isEntree && !allocateMode && (
                        <div className="bg-muted/40 border-input grid gap-3 rounded-lg border p-4">
                            <label className="flex cursor-pointer items-center gap-2 text-sm font-medium">
                                <Checkbox
                                    checked={fournisseurMode}
                                    onCheckedChange={(v) => {
                                        const next = v === true;
                                        setFournisseurMode(next);
                                        if (next) setLinked(false); // mutually exclusive with property link
                                    }}
                                />
                                <FileText className="text-muted-foreground h-4 w-4" />
                                <span>Dépense fournisseur / diverse</span>
                            </label>
                            {fournisseurMode && (
                                <div className="grid gap-2">
                                    <Label className="text-xs text-muted-foreground">Nom du fournisseur</Label>
                                    <Input
                                        value={fournisseurName}
                                        onChange={(e) => setFournisseurName(e.target.value)}
                                        placeholder="Ex : Café Slim, Plombier Ahmed, Carrefour…"
                                        className="h-10"
                                    />
                                    <Label className="text-xs text-muted-foreground mt-1">
                                        Facture / justificatif <span className="text-muted-foreground/70">(optionnel — à ajouter plus tard si non disponible)</span>
                                    </Label>
                                    <input
                                        type="file"
                                        accept="image/*,.pdf"
                                        onChange={(e) => setFactureFile(e.target.files?.[0] ?? null)}
                                        className="block w-full text-sm file:mr-2 file:rounded-md file:border-0 file:bg-muted file:px-3 file:py-1.5 file:text-xs file:font-medium hover:file:bg-muted/80"
                                    />
                                    {factureFile ? (
                                        <div className="flex items-center justify-between text-xs">
                                            <span className="text-muted-foreground truncate">
                                                📎 {factureFile.name} ({(factureFile.size / 1024).toFixed(1)} KB)
                                            </span>
                                            <button
                                                type="button"
                                                onClick={() => setFactureFile(null)}
                                                className="text-red-600 hover:underline"
                                            >
                                                retirer
                                            </button>
                                        </div>
                                    ) : (
                                        <p className="text-amber-700 text-[11px]">
                                            ⏳ Sans facture, la dépense sera marquée « facture en attente ». Vous pourrez l'ajouter plus tard depuis la ligne dans le tableau (⋮ → Modifier).
                                        </p>
                                    )}
                                </div>
                            )}
                        </div>
                    )}

                    {/* Optional source link — Encaisser only (link to a
                        reservation). The "lié à un logement" décaissement path
                        was removed (2026-06-30): a sortie is either a team
                        allocation or a fournisseur/divers expense. */}
                    {isEntree && (
                    <div className="bg-muted/40 border-input grid gap-3 rounded-lg border p-4">
                        <label className="flex cursor-pointer items-center gap-2 text-sm font-medium">
                            <Checkbox checked={linked} onCheckedChange={(v) => setLinked(v === true)} />
                            <Link2 className="text-muted-foreground h-4 w-4" />
                            <span>{linkLabel}</span>
                        </label>
                        {linked && (
                            <div className="grid gap-2">
                                {loadingItems ? (
                                    <div className="text-muted-foreground bg-background border-input flex h-12 items-center gap-2 rounded-md border px-3 text-sm">
                                        <Loader2 className="h-4 w-4 animate-spin" />
                                        {t('fond_de_caisse', 'link_loading')}
                                    </div>
                                ) : (
                                    <>
                                        {/* Date-range filter — Encaisser only.
                                            Narrows the reservation list by check-in date.
                                            Native date inputs: reliable picker, no portal
                                            issues inside the scrollable dialog. */}
                                        {isEntree && (
                                            <div className="grid grid-cols-2 gap-2">
                                                <div className="grid gap-1">
                                                    <Label className="text-[11px] text-muted-foreground">Arrivée — du</Label>
                                                    <Input
                                                        type="date"
                                                        value={resvFrom}
                                                        max={resvTo || undefined}
                                                        onChange={(e) => setResvFrom(e.target.value)}
                                                        className="h-9 text-sm"
                                                    />
                                                </div>
                                                <div className="grid gap-1">
                                                    <Label className="text-[11px] text-muted-foreground">Arrivée — au</Label>
                                                    <Input
                                                        type="date"
                                                        value={resvTo}
                                                        min={resvFrom || undefined}
                                                        onChange={(e) => setResvTo(e.target.value)}
                                                        className="h-9 text-sm"
                                                    />
                                                </div>
                                            </div>
                                        )}
                                        <SearchableSelect
                                            value={linkedId}
                                            onValueChange={setLinkedId}
                                            items={isEntree
                                                ? items.filter((it) => {
                                                    const d = it.date_from ?? '';
                                                    if (resvFrom && (!d || d < resvFrom)) return false;
                                                    if (resvTo && (!d || d > resvTo)) return false;
                                                    return true;
                                                })
                                                : items}
                                            placeholder={linkPickerPlaceholder}
                                            allOptionLabel=""
                                            notFoundMessage={t('fond_de_caisse', 'link_no_results')}
                                            className="bg-background text-foreground h-12 w-full justify-between rounded-md px-3 text-base font-normal"
                                            renderItem={isEntree ? renderReservationItem : undefined}
                                        />
                                        {isEntree && linkedId && (() => {
                                            const picked = items.find((it) => it.id.toString() === linkedId);
                                            if (!picked) return null;
                                            return <ReservationSummaryCard item={picked} />;
                                        })()}
                                        {/* Reclamation picker — Décaisser only.
                                            Lets the user attach a pending reclamation
                                            so it gets auto-marked as paid on submit. */}
                                        {!isEntree && reclamations.length > 0 && (
                                            <div className="grid gap-1.5 rounded-md border border-input bg-background px-3 py-2">
                                                <Label className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
                                                    Réclamation associée (optionnel) — {reclamations.length} ouverte(s)
                                                </Label>
                                                <select
                                                    value={pickedReclamationId}
                                                    onChange={(e) => setPickedReclamationId(e.target.value)}
                                                    className="h-10 w-full rounded-md border bg-background px-2 text-sm"
                                                >
                                                    <option value="">— Aucune —</option>
                                                    {reclamations.map((r) => (
                                                        <option key={r.id} value={r.id}>
                                                            #{r.id} · {r.property} · {r.title}
                                                            {r.priority ? ` (${r.priority})` : ''}
                                                        </option>
                                                    ))}
                                                </select>
                                                {pickedReclamationId && (
                                                    <p className="text-muted-foreground text-[11px]">
                                                        La réclamation sera marquée comme payée et liée à la dépense.
                                                    </p>
                                                )}
                                            </div>
                                        )}
                                        {isEntree && linkedId && (
                                            <ReceiptPicker
                                                receipts={receipts}
                                                loading={loadingReceipts}
                                                pickedId={pickedReceiptId}
                                                onPick={setPickedReceiptId}
                                                mode={receiptMode}
                                                onModeChange={setReceiptMode}
                                                receiptNote={receiptNote}
                                                onReceiptNoteChange={setReceiptNote}
                                            />
                                        )}
                                        {/* Facture upload — Décaisser + property linked.
                                            Attached to the Expense as its justificatif. */}
                                        {!isEntree && linkedId && (
                                            <div className="grid gap-1.5 rounded-md border border-input bg-background px-3 py-2">
                                                <Label className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
                                                    Facture / justificatif (optionnel)
                                                </Label>
                                                <input
                                                    type="file"
                                                    accept="image/*,.pdf"
                                                    onChange={(e) => setFactureFile(e.target.files?.[0] ?? null)}
                                                    className="block w-full text-sm file:mr-2 file:rounded-md file:border-0 file:bg-muted file:px-3 file:py-1.5 file:text-xs file:font-medium hover:file:bg-muted/80"
                                                />
                                                {factureFile && (
                                                    <div className="flex items-center justify-between text-xs">
                                                        <span className="text-muted-foreground truncate">
                                                            📎 {factureFile.name} ({(factureFile.size / 1024).toFixed(1)} KB)
                                                        </span>
                                                        <button
                                                            type="button"
                                                            onClick={() => setFactureFile(null)}
                                                            className="text-red-600 hover:underline"
                                                        >
                                                            retirer
                                                        </button>
                                                    </div>
                                                )}
                                            </div>
                                        )}
                                    </>
                                )}
                                <p className="text-muted-foreground text-xs">
                                    {isEntree
                                        ? t('fond_de_caisse', 'link_hint_reservation')
                                        : t('fond_de_caisse', 'link_hint_property')}
                                </p>
                            </div>
                        )}
                    </div>
                    )}
                </div>

                <SheetFooter className="flex-row justify-end border-t px-6 py-4">
                    <Button variant="outline" onClick={() => onOpenChange(false)} disabled={submitting}>
                        {t('fond_de_caisse', 'entry_cancel')}
                    </Button>
                    <Button onClick={submit} disabled={submitting || overpayBy > 0} className={buttonClass}>
                        {submitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Icon className="mr-2 h-4 w-4" />}
                        {isEntree ? t('fond_de_caisse', 'encaisser') : t('fond_de_caisse', 'decaisser')}
                    </Button>
                </SheetFooter>
            </SheetContent>
        </Sheet>
    );
}

/**
 * Compact summary card shown beneath the picker once a reservation is
 * selected: Montant total, Déjà payé, Reste à payer. Helps the user confirm
 * they picked the right reservation before clicking Encaisser.
 */
function ReservationSummaryCard({ item }: { item: PickerItem }) {
    // Picker only surfaces reservations with remaining > 0, so we can safely
    // assume total/paid/remaining are all real numbers here.
    const total = item.total_tnd ?? 0;
    const paid = item.paid_tnd ?? 0;
    const remaining = item.remaining_tnd ?? 0;

    const fmt = (n: number) =>
        n.toLocaleString('fr-TN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });

    return (
        <div className="bg-background border-input grid grid-cols-3 gap-2 rounded-md border p-3">
            <div className="flex min-w-0 flex-col gap-0.5">
                <span className="text-muted-foreground truncate text-[10px] uppercase tracking-wide">Montant total</span>
                <span className="truncate text-sm font-semibold tabular-nums">{fmt(total)} TND</span>
            </div>
            <div className="flex min-w-0 flex-col gap-0.5">
                <span className="text-muted-foreground truncate text-[10px] uppercase tracking-wide">Déjà payé</span>
                <span className="text-emerald-700 truncate text-sm font-semibold tabular-nums">
                    {fmt(paid)} TND
                </span>
            </div>
            <div className="flex min-w-0 flex-col gap-0.5">
                <span className="text-muted-foreground truncate text-[10px] uppercase tracking-wide">Reste à payer</span>
                <span className="text-amber-700 truncate text-sm font-semibold tabular-nums">
                    {fmt(remaining)} TND
                </span>
            </div>
        </div>
    );
}

/**
 * Rich row for a reservation in the picker: contract # / client name on top,
 * property + reste à payer underneath. Helps the user pick the right one
 * without guessing from the contract id alone.
 */
/**
 * Lists CASH+TND receipts attached to the picked reservation that aren't yet
 * tied to a Recovery. Choosing one will attach it to the new recovery instead
 * of creating a free entry — the receipt amount becomes the source of truth.
 */
type ReceiptModeT = 'none' | 'create_unsigned' | 'create_signed';

function ReceiptPicker({
    receipts,
    loading,
    pickedId,
    onPick,
    mode,
    onModeChange,
    receiptNote,
    onReceiptNoteChange,
}: {
    receipts: AvailableReceipt[];
    loading: boolean;
    pickedId: number | null;
    onPick: (id: number | null) => void;
    mode: ReceiptModeT;
    onModeChange: (m: ReceiptModeT) => void;
    receiptNote: string;
    onReceiptNoteChange: (v: string) => void;
}) {
    // Search box for the existing-receipts list. Cleared on close/open to
    // avoid stale state when switching reservations.
    const [search, setSearch] = React.useState('');

    const filteredReceipts = React.useMemo(() => {
        const q = search.trim().toLowerCase();
        if (!q) return receipts;
        return receipts.filter((r) => {
            const amount = r.amount.toLocaleString('fr-TN');
            const haystack = `${amount} ${r.currency} ${r.payment_mode} ${r.note ?? ''} ${frDate(r.date)} ${r.signature_status} ${r.giver_name ?? ''} ${r.receiver_name ?? ''}`.toLowerCase();
            return haystack.includes(q);
        });
    }, [receipts, search]);

    if (loading) {
        return (
            <div className="text-muted-foreground bg-background border-input flex items-center gap-2 rounded-md border px-3 py-2 text-sm">
                <Loader2 className="h-4 w-4 animate-spin" />
                Chargement des reçus…
            </div>
        );
    }

    const pickUnsigned = () => { onPick(null); onModeChange('create_unsigned'); };
    const pickSigned = () => { onPick(null); onModeChange('create_signed'); };

    const isUnsigned = pickedId === null && mode === 'create_unsigned';
    const isSigned   = pickedId === null && mode === 'create_signed';

    return (
        <div className="grid gap-2">
            <div className="text-muted-foreground flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide">
                <FileText className="h-3 w-3" />
                Reçu de paiement {receipts.length > 0 && <span className="normal-case">— {receipts.length} disponible(s)</span>}
            </div>
            <div className="grid gap-1.5">
                {/* Mode 1: create receipt without signature */}
                <button
                    type="button"
                    onClick={pickUnsigned}
                    className={`bg-background border-input flex items-center justify-between gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors hover:bg-accent ${
                        isUnsigned ? 'border-emerald-500 ring-1 ring-emerald-500/30' : ''
                    }`}
                >
                    <span className="flex flex-col">
                        <span className="font-medium">Créer un reçu (sans signature)</span>
                        <span className="text-muted-foreground text-[11px]">
                            Reçu en attente — la cash n'est comptée que via la pièce liée
                        </span>
                    </span>
                    {isUnsigned && <CheckCircle2 className="h-4 w-4 text-emerald-600" />}
                </button>

                {/* Mode 3: create receipt with signature (redirects to signing page) */}
                <button
                    type="button"
                    onClick={pickSigned}
                    className={`bg-background border-input flex items-center justify-between gap-2 rounded-md border px-3 py-2 text-left text-sm transition-colors hover:bg-accent ${
                        isSigned ? 'border-emerald-500 ring-1 ring-emerald-500/30' : ''
                    }`}
                >
                    <span className="flex flex-col">
                        <span className="font-medium">Créer un reçu (avec signature)</span>
                        <span className="text-muted-foreground text-[11px]">
                            Redirige vers la signature — choisir le donneur / receveur
                        </span>
                    </span>
                    {isSigned && <CheckCircle2 className="h-4 w-4 text-emerald-600" />}
                </button>

                {(isUnsigned || isSigned) && (
                    <div className="grid gap-1.5 rounded-md bg-muted/30 px-3 py-2">
                        <Label className="text-xs text-muted-foreground">Note du reçu (optionnel)</Label>
                        <Input
                            value={receiptNote}
                            onChange={(e) => onReceiptNoteChange(e.target.value)}
                            placeholder="Ex : Acompte, solde, intervention plombier…"
                            className="h-10 text-sm"
                        />
                    </div>
                )}

                {/* Existing receipts available for attachment (entrée only) */}
                {receipts.length > 0 && (
                    <>
                        <div className="text-muted-foreground mt-1 flex items-center justify-between text-[11px] font-medium uppercase tracking-wide">
                            <span>Ou attacher un reçu existant</span>
                            {search && (
                                <span className="text-[10px] normal-case">
                                    {filteredReceipts.length}/{receipts.length}
                                </span>
                            )}
                        </div>
                        {/* Search — visible whenever there's more than 3 receipts
                            to filter; toggles into focus as soon as the user types. */}
                        {receipts.length > 3 && (
                            <div className="relative">
                                <Search className="text-muted-foreground absolute top-1/2 left-2 h-3.5 w-3.5 -translate-y-1/2" />
                                <Input
                                    value={search}
                                    onChange={(e) => setSearch(e.target.value)}
                                    placeholder="Rechercher (montant, donneur, receveur, note, date…)"
                                    className="h-9 pl-7 text-sm"
                                />
                            </div>
                        )}
                        {filteredReceipts.length === 0 && (
                            <div className="text-muted-foreground rounded-md border border-dashed px-3 py-2 text-xs">
                                Aucun reçu ne correspond à « {search} ».
                            </div>
                        )}
                        {filteredReceipts.map((r) => {
                            const selected = pickedId === r.id;
                            return (
                                <button
                                    type="button"
                                    key={r.id}
                                    onClick={() => { onPick(r.id); onModeChange('none'); }}
                                    className={`bg-background border-input flex items-start justify-between gap-3 rounded-md border px-3 py-2 text-left text-sm transition-colors hover:bg-accent ${
                                        selected ? 'border-emerald-500 ring-1 ring-emerald-500/30' : ''
                                    }`}
                                >
                                    <div className="flex min-w-0 flex-col gap-0.5">
                                        <div className="flex items-center gap-2">
                                            <span className="font-medium tabular-nums">
                                                {r.amount.toLocaleString('fr-TN', { minimumFractionDigits: 2 })} {r.currency}
                                            </span>
                                            <span className="text-muted-foreground text-xs">{frDate(r.date)}</span>
                                            <span
                                                className={`rounded-full px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide ${
                                                    r.signature_status === 'signed'
                                                        ? 'bg-emerald-100 text-emerald-700'
                                                        : 'bg-amber-100 text-amber-700'
                                                }`}
                                            >
                                                {r.signature_status === 'signed' ? 'Signé' : 'Non signé'}
                                            </span>
                                        </div>
                                        {(r.giver_name || r.receiver_name) && (
                                            <span className="text-muted-foreground flex items-center gap-1 truncate text-[11px]">
                                                <span className="font-medium">{r.giver_name ?? '—'}</span>
                                                <span className="opacity-60">→</span>
                                                <span className="font-medium">{r.receiver_name ?? '—'}</span>
                                            </span>
                                        )}
                                        {r.note && (
                                            <span className="text-muted-foreground truncate text-xs">{r.note}</span>
                                        )}
                                    </div>
                                    {selected && <CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600" />}
                                </button>
                            );
                        })}
                    </>
                )}
            </div>
            {pickedId !== null && (
                <p className="text-muted-foreground text-xs">
                    Le montant est verrouillé sur le reçu sélectionné.
                </p>
            )}
        </div>
    );
}

function renderReservationItem(item: { id: number; name: string; [k: string]: unknown }) {
    const propertyName = (item.property_name as string | undefined) ?? '';
    const remaining    = (item.remaining_tnd as number | undefined) ?? 0;
    const total        = (item.total_tnd as number | undefined) ?? 0;
    const dateFrom     = (item.date_from as string | undefined) ?? null;
    const dateTo       = (item.date_to   as string | undefined) ?? null;

    const dateRange = dateFrom && dateTo
        ? `${frDate(dateFrom)} → ${frDate(dateTo)}`
        : dateFrom ? frDate(dateFrom) : null;

    return (
        <div className="flex w-full min-w-0 flex-col gap-0.5">
            <span className="truncate text-sm font-medium">{item.name}</span>
            <span className="text-muted-foreground flex flex-wrap items-center gap-3 text-xs">
                {propertyName && (
                    <span className="inline-flex min-w-0 items-center gap-1 truncate">
                        <Building2 className="h-3 w-3 shrink-0" />
                        <span className="truncate">{propertyName}</span>
                    </span>
                )}
                {dateRange && (
                    <span className="inline-flex shrink-0 items-center gap-1">
                        <CalendarDays className="h-3 w-3" />
                        {dateRange}
                    </span>
                )}
                <span className="text-amber-700 inline-flex shrink-0 items-center gap-1">
                    <Wallet className="h-3 w-3" />
                    Reste {remaining.toLocaleString('fr-TN', { minimumFractionDigits: 2 })} TND
                    <span className="text-muted-foreground/70 ml-1">
                        / {total.toLocaleString('fr-TN', { minimumFractionDigits: 0 })} TND
                    </span>
                </span>
            </span>
        </div>
    );
}
