import { DateRangePicker } from '@/components/date-range';
import {
    AlertDialog,
    AlertDialogAction,
    AlertDialogCancel,
    AlertDialogContent,
    AlertDialogDescription,
    AlertDialogFooter,
    AlertDialogHeader,
    AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { useTranslation } from '@/hooks/use-translation';
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router, usePage } from '@inertiajs/react';
import { AlertTriangle, ArrowDown, ArrowDownCircle, ArrowUp, ArrowUpCircle, ArrowUpDown, FileDown, RefreshCcw, Trash2, Upload, Wallet, X } from 'lucide-react';
import * as React from 'react';
import { toast } from 'sonner';
import { EditableRow } from './editable-row';
import { MobileLedgerCard } from './mobile-row';
import { EntryDialog } from './entry-dialog';
import { ExpenseEditDialog } from './expense-edit-dialog';
import { ImportDialog } from './import-dialog';
import { RecoveryEditDialog } from './recovery-edit-dialog';
import { SourceDetailDialog } from './source-detail-dialog';
import type { LedgerFilters, LedgerRow, LedgerSource, LedgerSummary } from './types';

interface PageProps {
    rows: LedgerRow[];
    filters: LedgerFilters;
    summary: LedgerSummary;
    meta?: {
        total_matching: number;
        displayed: number;
        limit: number;
        truncated: boolean;
        page: number;
        per_page: number;
        total_pages: number;
        from: number;
        to: number;
    };
    flash?: { type: 'success' | 'error'; message: string };
    isAdmin?: boolean;
    currentUser?: { id: number; name: string };
    entrepriseOwnerId?: number | null;
    teamMembers?: { id: number; name: string }[];
    [key: string]: unknown;
}

/**
 * Filter label with an optional inline "×" clear button — lets the user
 * dismiss a single filter without resetting the whole bar.
 */
function FilterLabel({
    children,
    active,
    onClear,
}: {
    children: React.ReactNode;
    active: boolean;
    onClear: () => void;
}) {
    return (
        <div className="flex items-center gap-1">
            <Label className="text-xs">{children}</Label>
            {active && (
                <button
                    type="button"
                    onClick={onClear}
                    title="Retirer ce filtre"
                    className="text-muted-foreground hover:bg-red-100 hover:text-red-600 flex h-3.5 w-3.5 items-center justify-center rounded-full transition-colors"
                >
                    <X className="h-2.5 w-2.5" />
                </button>
            )}
        </div>
    );
}

function formatTnd(n: number) {
    return n.toLocaleString('fr-TN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' TND';
}

export default function Index() {
    const { t, locale } = useTranslation();
    const { rows, filters, summary, meta, flash, isAdmin, currentUser, entrepriseOwnerId, teamMembers } = usePage<PageProps>().props;
    // The owner id behind the "Caisse de l'entreprise" option (falls back to the
    // viewing admin for franchises / unconfigured HQ).
    const entrepriseId = String(entrepriseOwnerId ?? currentUser?.id ?? '');
    const [importOpen, setImportOpen] = React.useState(false);
    const [resetOpen, setResetOpen] = React.useState(false);
    const [entryDialog, setEntryDialog] = React.useState<'entree' | 'sortie' | null>(null);
    const [sourceDialog, setSourceDialog] = React.useState<{ source: LedgerSource; id: number } | null>(null);
    const [editDialog, setEditDialog] = React.useState<{ source: LedgerSource; id: number } | null>(null);
    const [deleteDialog, setDeleteDialog] = React.useState<{ source: LedgerSource; id: number } | null>(null);

    // Defensive: when ALL dialogs return to closed, ensure no Radix leftover
    // body lock keeps the page un-clickable. Radix usually cleans up on close
    // but stacked / conditionally-mounted dialogs can leak `pointer-events:none`
    // on <body>. We reset it explicitly after a short delay (after animations).
    const anyOpen = importOpen
        || resetOpen
        || entryDialog !== null
        || sourceDialog !== null
        || editDialog !== null
        || deleteDialog !== null;
    React.useEffect(() => {
        if (anyOpen) return;
        const t = setTimeout(() => {
            if (typeof document !== 'undefined') {
                document.body.style.pointerEvents = '';
                document.body.style.removeProperty('pointer-events');
            }
        }, 300);
        return () => clearTimeout(t);
    }, [anyOpen]);

    // Live updates — keep the ledger fresh so new to-dos (à approuver,
    // à traiter, retour…) and new entries appear without a manual refresh.
    // Partial reload of just the data props every 10s. Paused while a
    // dialog is open (don't disrupt input) or the tab is in the background.
    React.useEffect(() => {
        if (anyOpen) return;
        const id = setInterval(() => {
            if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;
            router.reload({
                only: ['rows', 'summary', 'meta'],
                preserveScroll: true,
                preserveState: true,
            });
        }, 10000);
        return () => clearInterval(id);
    }, [anyOpen]);

    // Refresh immediately when the tab regains focus — so switching back
    // to the page never shows stale data.
    React.useEffect(() => {
        const onFocus = () => {
            if (anyOpen) return;
            router.reload({ only: ['rows', 'summary', 'meta'], preserveScroll: true, preserveState: true });
        };
        window.addEventListener('focus', onFocus);
        return () => window.removeEventListener('focus', onFocus);
    }, [anyOpen]);

    const [selected, setSelected] = React.useState<Set<number>>(new Set());

    // Active rows that can be bulk-deleted (deleted rows are already gone)
    const selectableIds = React.useMemo(
        () => rows.filter((r) => r.deleted_at === null).map((r) => r.id),
        [rows],
    );

    // Clear selection whenever the visible row set changes (after filter, import, etc.)
    React.useEffect(() => {
        setSelected((prev) => {
            const next = new Set<number>();
            for (const id of prev) if (selectableIds.includes(id)) next.add(id);
            return next;
        });
    }, [selectableIds.join(',')]);

    const allSelected = selectableIds.length > 0 && selected.size === selectableIds.length;
    const someSelected = selected.size > 0 && !allSelected;

    const toggleAll = (checked: boolean) => {
        setSelected(checked ? new Set(selectableIds) : new Set());
    };
    const toggleOne = (id: number, checked: boolean) => {
        setSelected((prev) => {
            const next = new Set(prev);
            if (checked) next.add(id); else next.delete(id);
            return next;
        });
    };

    const bulkDelete = () => {
        if (selected.size === 0) return;
        if (!confirm(`Supprimer ${selected.size} ligne(s) sélectionnée(s) ?`)) return;
        router.post(
            route('fond-de-caisse.bulk-delete'),
            { ids: Array.from(selected) },
            {
                preserveScroll: true,
                only: ['rows', 'summary', 'flash'],
                onSuccess: () => setSelected(new Set()),
            },
        );
    };

    const resetAll = () => {
        router.post(
            route('fond-de-caisse.reset'),
            {},
            {
                preserveScroll: true,
                only: ['rows', 'summary', 'flash'],
                onSuccess: () => {
                    setSelected(new Set());
                    setResetOpen(false);
                },
            },
        );
    };

    const breadcrumbs: BreadcrumbItem[] = [
        { title: t('fond_de_caisse', 'title'), href: '/fond-de-caisse' },
    ];

    const [search, setSearch] = React.useState(filters.search ?? '');
    // Period is a single dropdown value: '' (all), 'm:YYYY-MM' (specific month),
    // or 'y:YYYY' (full year). We split it into month/year on submit.
    const initialPeriod = filters.year ? `y:${filters.year}` : filters.month ? `m:${filters.month}` : '';
    const [period, setPeriod] = React.useState<string>(initialPeriod);
    const [type, setType] = React.useState<string>(filters.type ?? 'all');
    const [status, setStatus] = React.useState<string>(filters.status ?? 'active');
    const [source, setSource] = React.useState<string>(filters.source ?? 'all');
    const [perPage, setPerPage] = React.useState<string>((filters as any).per_page ?? '20');
    const [page, setPage] = React.useState<number>((filters as any).page ?? 1);
    const [sort, setSort] = React.useState<'asc' | 'desc'>(filters.sort ?? 'desc');
    // Admin-only: filter the ledger by a specific team member.
    // '' = all users (only admins can pick that).
    const [owner, setOwner] = React.useState<string>(filters.owner ? String(filters.owner) : '');
    const [dateRange, setDateRange] = React.useState<{ from: string | null; to: string | null }>({
        from: filters.from ?? null,
        to: filters.to ?? null,
    });

    // Decode the period dropdown value into the (month, year) backend params.
    const periodToParams = (p: string): { month?: string; year?: string } => {
        if (p.startsWith('m:')) return { month: p.slice(2) };
        if (p.startsWith('y:')) return { year: p.slice(2) };
        return {};
    };

    // debounce filter changes
    const isFirst = React.useRef(true);
    React.useEffect(() => {
        if (isFirst.current) {
            isFirst.current = false;
            return;
        }
        const timer = setTimeout(() => {
            const periodParams = periodToParams(period);
            router.get(
                route('fond-de-caisse.index'),
                {
                    ...periodParams,
                    from: dateRange.from || undefined,
                    to: dateRange.to || undefined,
                    search: search || undefined,
                    type: type !== 'all' ? type : undefined,
                    status: status !== 'active' ? status : undefined,
                    source: source !== 'all' ? source : undefined,
                    per_page: perPage !== '20' ? perPage : undefined,
                    page: page > 1 ? page : undefined,
                    sort: sort !== 'desc' ? sort : undefined,
                    owner: owner || undefined,
                },
                { preserveState: true, preserveScroll: true, replace: true, only: ['rows', 'summary', 'filters', 'meta', 'teamMembers'] },
            );
        }, 350);
        return () => clearTimeout(timer);
    }, [period, dateRange.from, dateRange.to, search, type, status, source, perPage, page, sort, owner]);

    // Reset page to 1 whenever any FILTER changes (but not when only `page` itself
    // moves) — otherwise switching from Mai 2026 to Avril 2026 might land on a
    // page that doesn't exist in the new window.
    const isFirstFilterChange = React.useRef(true);
    React.useEffect(() => {
        if (isFirstFilterChange.current) {
            isFirstFilterChange.current = false;
            return;
        }
        setPage(1);
    }, [period, dateRange.from, dateRange.to, search, type, status, source, perPage]);

    React.useEffect(() => {
        if (flash?.type === 'success') toast.success(flash.message);
        else if (flash?.type === 'error') toast.error(flash.message);
    }, [flash]);

    const resetFilters = () => {
        setSearch('');
        setPeriod('');
        setType('all');
        setStatus('active');
        setSource('all');
        setOwner('');
        setPerPage('20');
        setPage(1);
        setDateRange({ from: null, to: null });
    };

    const exportParams = () => {
        const params = new URLSearchParams();
        const p = periodToParams(period);
        if (p.month) params.set('month', p.month);
        if (p.year)  params.set('year', p.year);
        if (dateRange.from) params.set('from', dateRange.from);
        if (dateRange.to) params.set('to', dateRange.to);
        if (search) params.set('search', search);
        if (type !== 'all') params.set('type', type);
        if (status !== 'active') params.set('status', status);
        if (source !== 'all') params.set('source', source);
        return params;
    };
    const exportPdf   = () => window.open(`/fond-de-caisse/export/pdf?${exportParams().toString()}`, '_blank');
    const exportExcel = () => { window.location.href = `/fond-de-caisse/export/excel?${exportParams().toString()}`; };

    // Period options: current/last year as presets, then 5 recent years, then
    // the last 24 months. Values are prefixed `y:` / `m:` so the backend
    // knows which filter to apply.
    const localeForFormat = (locale ?? 'fr').startsWith('en') ? 'en-US' : 'fr-FR';
    const periodOptions = React.useMemo(() => {
        const groups: { label: string; opts: { value: string; label: string }[] }[] = [];
        const now = new Date();
        const currentYear = now.getFullYear();

        groups.push({
            label: '',
            opts: [
                { value: `y:${currentYear}`,     label: t('fond_de_caisse', 'period_this_year') },
                { value: `y:${currentYear - 1}`, label: t('fond_de_caisse', 'period_last_year') },
            ],
        });

        const yearOpts: { value: string; label: string }[] = [];
        for (let i = 2; i < 5; i++) {
            const y = currentYear - i;
            yearOpts.push({ value: `y:${y}`, label: t('fond_de_caisse', 'period_year_label').replace(':year', String(y)) });
        }
        groups.push({ label: '', opts: yearOpts });

        const monthOpts: { value: string; label: string }[] = [];
        for (let i = 0; i < 24; i++) {
            const d = new Date(currentYear, now.getMonth() - i, 1);
            const value = `m:${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
            const label = d.toLocaleDateString(localeForFormat, { month: 'long', year: 'numeric' });
            monthOpts.push({ value, label });
        }
        groups.push({ label: '', opts: monthOpts });

        return groups;
    }, [t, locale]);

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title={t('fond_de_caisse', 'title')} />

            <div className="flex h-full flex-1 flex-col gap-4 p-3 sm:p-4 lg:p-6">
                {/* Header */}
                <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <div>
                        <h1 className="text-2xl font-semibold tracking-tight">{t('fond_de_caisse', 'title')}</h1>
                        <p className="text-muted-foreground text-sm">{t('fond_de_caisse', 'subtitle')}</p>
                    </div>
                    <div className="flex flex-wrap items-center gap-2">
                        {/* Encaisser / Décaisser / Importer / Réinitialiser are
                            admin-only. Team members can only consult + export. */}
                        {isAdmin && (
                            <>
                                <Button
                                    onClick={() => setEntryDialog('entree')}
                                    className="bg-emerald-700 text-white hover:bg-emerald-800"
                                >
                                    <ArrowUpCircle className="mr-2 h-4 w-4" />
                                    {t('fond_de_caisse', 'encaisser')}
                                </Button>
                                <Button
                                    onClick={() => setEntryDialog('sortie')}
                                    className="bg-red-700 text-white hover:bg-red-800"
                                >
                                    <ArrowDownCircle className="mr-2 h-4 w-4" />
                                    {t('fond_de_caisse', 'decaisser')}
                                </Button>
                                <Button variant="outline" onClick={() => setImportOpen(true)}>
                                    <Upload className="mr-2 h-4 w-4" />
                                    {t('fond_de_caisse', 'importer')}
                                </Button>
                            </>
                        )}
                        <Button variant="outline" onClick={exportPdf}>
                            <FileDown className="mr-2 h-4 w-4" />
                            {t('fond_de_caisse', 'export_pdf')}
                        </Button>
                        <Button variant="outline" onClick={exportExcel}>
                            <FileDown className="mr-2 h-4 w-4" />
                            {t('fond_de_caisse', 'export_excel')}
                        </Button>
                        {isAdmin && (
                            <Button
                                variant="outline"
                                onClick={() => setResetOpen(true)}
                                className="text-red-700 border-red-300 hover:bg-red-50 hover:text-red-800"
                            >
                                <AlertTriangle className="mr-2 h-4 w-4" />
                                {t('fond_de_caisse', 'reinitialiser')}
                            </Button>
                        )}
                    </div>
                </div>

                {/* Filters */}
                <Card>
                    <CardContent className="flex flex-col flex-wrap gap-3 px-3 py-3 sm:flex-row sm:items-end">
                        <div className="flex flex-col gap-1">
                            <FilterLabel active={period !== ''} onClear={() => setPeriod('')}>
                                {t('fond_de_caisse', 'filter_period')}
                            </FilterLabel>
                            <Select value={period || 'all'} onValueChange={(v) => setPeriod(v === 'all' ? '' : v)}>
                                <SelectTrigger className="h-9 w-[220px]">
                                    <SelectValue placeholder={t('fond_de_caisse', 'filter_period')} />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectItem value="all">{t('fond_de_caisse', 'period_all')}</SelectItem>
                                    {periodOptions.map((group, gi) => (
                                        <React.Fragment key={gi}>
                                            {gi > 0 && <div className="bg-muted my-1 h-px" />}
                                            {group.opts.map((o) => (
                                                <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
                                            ))}
                                        </React.Fragment>
                                    ))}
                                </SelectContent>
                            </Select>
                        </div>

                        <div className="flex flex-col gap-1">
                            <FilterLabel
                                active={dateRange.from !== null || dateRange.to !== null}
                                onClear={() => setDateRange({ from: null, to: null })}
                            >
                                {t('fond_de_caisse', 'filter_range')}
                            </FilterLabel>
                            <DateRangePicker
                                date={{ from: dateRange.from ?? '', to: dateRange.to ?? '' }}
                                setDate={(d: any) => setDateRange({ from: d?.from || null, to: d?.to || null })}
                            />
                        </div>

                        <div className="flex flex-col gap-1">
                            <FilterLabel active={type !== 'all'} onClear={() => setType('all')}>
                                {t('fond_de_caisse', 'filter_type')}
                            </FilterLabel>
                            <Select value={type} onValueChange={setType}>
                                <SelectTrigger className="h-9 w-[140px]">
                                    <SelectValue />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectItem value="all">{t('fond_de_caisse', 'type_all')}</SelectItem>
                                    <SelectItem value="entree">{t('fond_de_caisse', 'type_entree')}</SelectItem>
                                    <SelectItem value="sortie">{t('fond_de_caisse', 'type_sortie')}</SelectItem>
                                </SelectContent>
                            </Select>
                        </div>

                        <div className="flex flex-col gap-1">
                            <FilterLabel active={status !== 'active'} onClear={() => setStatus('active')}>
                                {t('fond_de_caisse', 'filter_status')}
                            </FilterLabel>
                            <Select value={status} onValueChange={setStatus}>
                                <SelectTrigger className="h-9 w-[160px]">
                                    <SelectValue />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectItem value="active">{t('fond_de_caisse', 'status_active')}</SelectItem>
                                    <SelectItem value="deleted">{t('fond_de_caisse', 'status_deleted')}</SelectItem>
                                    <SelectItem value="all">{t('fond_de_caisse', 'status_all')}</SelectItem>
                                </SelectContent>
                            </Select>
                        </div>

                        <div className="flex flex-col gap-1">
                            <FilterLabel active={source !== 'all'} onClear={() => setSource('all')}>
                                {t('fond_de_caisse', 'filter_source')}
                            </FilterLabel>
                            <Select value={source} onValueChange={setSource}>
                                <SelectTrigger className="h-9 w-[180px]">
                                    <SelectValue />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectItem value="all">{t('fond_de_caisse', 'source_all')}</SelectItem>
                                    <SelectItem value="manual">{t('fond_de_caisse', 'source_manual')}</SelectItem>
                                    <SelectItem value="recovery">{t('fond_de_caisse', 'source_recovery')}</SelectItem>
                                    <SelectItem value="expense">{t('fond_de_caisse', 'source_expense')}</SelectItem>
                                    <SelectItem value="allocation">Allocation</SelectItem>
                                </SelectContent>
                            </Select>
                        </div>

                        {/* Caisse filter — admin only. Default = the entreprise
                            caisse (the admin's own central caisse: allocations
                            given, returns received, recoveries, expenses).
                            They can drill into any team member's float. */}
                        {isAdmin && (
                            <div className="flex flex-col gap-1">
                                <FilterLabel
                                    active={!!owner && owner !== entrepriseId}
                                    onClear={() => setOwner(entrepriseId)}
                                >
                                    Caisse
                                </FilterLabel>
                                <Select
                                    value={owner || entrepriseId}
                                    onValueChange={(v) => setOwner(v)}
                                >
                                    <SelectTrigger className="h-9 w-[210px]">
                                        <SelectValue />
                                    </SelectTrigger>
                                    <SelectContent>
                                        {entrepriseId && (
                                            <SelectItem value={entrepriseId}>
                                                Caisse de l'entreprise
                                            </SelectItem>
                                        )}
                                        {(teamMembers ?? [])
                                            .filter((u) => String(u.id) !== entrepriseId)
                                            .map((u) => (
                                                <SelectItem key={u.id} value={String(u.id)}>{u.name}</SelectItem>
                                            ))}
                                    </SelectContent>
                                </Select>
                            </div>
                        )}

                        <div className="flex flex-1 flex-col gap-1 sm:min-w-[200px]">
                            <FilterLabel active={search !== ''} onClear={() => setSearch('')}>
                                {t('fond_de_caisse', 'filter_search')}
                            </FilterLabel>
                            <Input
                                type="search"
                                value={search}
                                onChange={(e) => setSearch(e.target.value)}
                                placeholder={t('fond_de_caisse', 'designation_placeholder')}
                                className="h-9"
                            />
                        </div>

                        <Button variant="ghost" size="sm" onClick={resetFilters} className="self-end">
                            <RefreshCcw className="mr-2 h-4 w-4" />
                            {t('fond_de_caisse', 'reset_filters')}
                        </Button>
                    </CardContent>
                </Card>

                {/* Summary cards */}
                <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
                    <SummaryCard
                        icon={<ArrowUpCircle className="h-5 w-5 text-emerald-600" />}
                        label={t('fond_de_caisse', 'total_entrees')}
                        value={formatTnd(summary.total_entrees)}
                        accent="emerald"
                    />
                    <SummaryCard
                        icon={<ArrowDownCircle className="h-5 w-5 text-red-600" />}
                        label={t('fond_de_caisse', 'total_sorties')}
                        value={formatTnd(summary.total_sorties)}
                        accent="red"
                    />
                    <SummaryCard
                        icon={<Wallet className="h-5 w-5" />}
                        label={t('fond_de_caisse', 'current_solde')}
                        value={formatTnd(summary.current_solde)}
                        accent={summary.current_solde >= 0 ? 'emerald' : 'red'}
                        emphasis
                    />
                </div>

                {/* Truncation notice — kept as safety net for the "Tout" option
                    when the filter has more rows than the hard cap (rowLimit). */}
                {meta?.truncated && (
                    <div className="border-amber-300 bg-amber-50 text-amber-800 flex items-center gap-2 rounded-md border px-4 py-2 text-sm">
                        <AlertTriangle className="h-4 w-4" />
                        {t('fond_de_caisse', 'truncated_notice')
                            .replace(':limit', String(meta.limit))
                            .replace(':total', meta.total_matching.toLocaleString(localeForFormat))}
                    </div>
                )}

                {/* Bulk action bar — appears when rows are selected */}
                {selected.size > 0 && (
                    <div className="bg-secondary/10 border-secondary/40 flex items-center justify-between rounded-md border px-4 py-2">
                        <span className="text-sm font-medium">
                            {selected.size === 1
                                ? t('fond_de_caisse', 'bulk_selected_one')
                                : t('fond_de_caisse', 'bulk_selected_many').replace(':count', String(selected.size))}
                        </span>
                        <div className="flex items-center gap-2">
                            <Button variant="ghost" size="sm" onClick={() => setSelected(new Set())}>
                                {t('fond_de_caisse', 'bulk_cancel')}
                            </Button>
                            <Button variant="destructive" size="sm" onClick={bulkDelete}>
                                <Trash2 className="mr-2 h-4 w-4" />
                                {t('fond_de_caisse', 'bulk_delete').replace(':count', String(selected.size))}
                            </Button>
                        </div>
                    </div>
                )}

                {/* Table */}
                <Card className="flex-1 overflow-hidden">
                    <CardContent className="p-0">
                        {/* Desktop: table. Mobile: card list. */}
                        <div className="hidden overflow-x-auto md:block">
                            <Table className="table-fixed w-full text-base">
                                <colgroup>
                                    <col style={{ width: '52px' }} />
                                    <col style={{ width: '200px' }} />
                                    <col style={{ width: '360px' }} />
                                    <col style={{ width: '240px' }} />
                                    <col style={{ width: '240px' }} />
                                    <col style={{ width: '220px' }} />
                                    <col style={{ width: '72px' }} />
                                </colgroup>
                                <TableHeader>
                                    <TableRow className="bg-muted/50 [&_th]:h-12 [&_th]:text-sm [&_th]:font-semibold">
                                        <TableHead className="text-center">
                                            <Checkbox
                                                checked={allSelected ? true : someSelected ? 'indeterminate' : false}
                                                onCheckedChange={(v) => toggleAll(v === true)}
                                                disabled={selectableIds.length === 0}
                                                aria-label="Tout sélectionner"
                                            />
                                        </TableHead>
                                        <TableHead>
                                            <button
                                                type="button"
                                                onClick={() => setSort(sort === 'desc' ? 'asc' : 'desc')}
                                                className="inline-flex items-center gap-1 hover:text-foreground transition-colors"
                                                aria-label="Trier par date"
                                                title={`Tri ${sort === 'desc' ? 'plus récent en haut' : 'plus ancien en haut'} — cliquer pour inverser`}
                                            >
                                                {t('fond_de_caisse', 'col_date')}
                                                {sort === 'desc'
                                                    ? <ArrowDown className="h-3.5 w-3.5 opacity-70" />
                                                    : <ArrowUp className="h-3.5 w-3.5 opacity-70" />}
                                            </button>
                                        </TableHead>
                                        <TableHead>{t('fond_de_caisse', 'col_designation')}</TableHead>
                                        <TableHead className="text-right pr-4">{t('fond_de_caisse', 'col_entree')}</TableHead>
                                        <TableHead className="text-right pr-4">{t('fond_de_caisse', 'col_sortie')}</TableHead>
                                        <TableHead className="text-right pr-4">{t('fond_de_caisse', 'col_solde')}</TableHead>
                                        <TableHead></TableHead>
                                    </TableRow>
                                </TableHeader>
                                <TableBody>
                                    {rows.length === 0 && (
                                        <TableRow>
                                            <TableCell colSpan={7} className="text-muted-foreground py-8 text-center text-sm">
                                                {t('fond_de_caisse', 'no_entries')}
                                            </TableCell>
                                        </TableRow>
                                    )}
                                    {rows.map((row) => (
                                        <EditableRow
                                            key={row.id}
                                            row={row}
                                            selected={selected.has(row.id)}
                                            onToggleSelect={(checked) => toggleOne(row.id, checked)}
                                            onOpenSource={(source, id) => setSourceDialog({ source, id })}
                                            onEditSource={(source, id) => setEditDialog({ source, id })}
                                            onDeleteSource={(source, id) => setDeleteDialog({ source, id })}
                                        />
                                    ))}
                                </TableBody>
                            </Table>
                        </div>

                        {/* Mobile: card list */}
                        <div className="flex flex-col gap-2 p-3 md:hidden">
                            {rows.length === 0 && (
                                <div className="text-muted-foreground py-8 text-center text-sm">
                                    {t('fond_de_caisse', 'no_entries')}
                                </div>
                            )}
                            {rows.map((row) => (
                                <MobileLedgerCard
                                    key={row.id}
                                    row={row}
                                    selected={selected.has(row.id)}
                                    onToggleSelect={(checked) => toggleOne(row.id, checked)}
                                    onOpenSource={(source, id) => setSourceDialog({ source, id })}
                                    onEditSource={(source, id) => setEditDialog({ source, id })}
                                    onDeleteSource={(source, id) => setDeleteDialog({ source, id })}
                                />
                            ))}
                        </div>

                        {/* Pagination footer */}
                        {meta && (
                            <div className="flex flex-col gap-3 border-t px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
                                <div className="text-muted-foreground flex items-center gap-3 text-sm">
                                    <span>Affichage</span>
                                    <Select value={perPage} onValueChange={setPerPage}>
                                        <SelectTrigger className="h-8 w-[80px]">
                                            <SelectValue />
                                        </SelectTrigger>
                                        <SelectContent>
                                            <SelectItem value="10">10</SelectItem>
                                            <SelectItem value="20">20</SelectItem>
                                            <SelectItem value="30">30</SelectItem>
                                            <SelectItem value="40">40</SelectItem>
                                            <SelectItem value="50">50</SelectItem>
                                            <SelectItem value="all">Tout</SelectItem>
                                        </SelectContent>
                                    </Select>
                                    <span className="tabular-nums">
                                        {meta.from}–{meta.to} sur {meta.total_matching.toLocaleString(localeForFormat)}
                                    </span>
                                </div>
                                <div className="flex items-center gap-1">
                                    <Button
                                        variant="outline"
                                        size="sm"
                                        disabled={meta.page <= 1}
                                        onClick={() => setPage(1)}
                                    >
                                        «
                                    </Button>
                                    <Button
                                        variant="outline"
                                        size="sm"
                                        disabled={meta.page <= 1}
                                        onClick={() => setPage(Math.max(1, meta.page - 1))}
                                    >
                                        ‹
                                    </Button>
                                    <span className="text-muted-foreground px-2 text-sm tabular-nums">
                                        Page {meta.page} / {meta.total_pages}
                                    </span>
                                    <Button
                                        variant="outline"
                                        size="sm"
                                        disabled={meta.page >= meta.total_pages}
                                        onClick={() => setPage(Math.min(meta.total_pages, meta.page + 1))}
                                    >
                                        ›
                                    </Button>
                                    <Button
                                        variant="outline"
                                        size="sm"
                                        disabled={meta.page >= meta.total_pages}
                                        onClick={() => setPage(meta.total_pages)}
                                    >
                                        »
                                    </Button>
                                </div>
                            </div>
                        )}
                    </CardContent>
                </Card>
            </div>

            <ImportDialog open={importOpen} onOpenChange={setImportOpen} />

            {/* All dialogs always-mounted to avoid Radix body-lock leaks when
                a component is unmounted before its close animation completes. */}
            <EntryDialog
                open={entryDialog !== null}
                onOpenChange={(v) => !v && setEntryDialog(null)}
                type={entryDialog ?? 'entree'}
            />

            <SourceDetailDialog
                open={sourceDialog !== null}
                onOpenChange={(v) => !v && setSourceDialog(null)}
                source={sourceDialog?.source ?? 'manual'}
                sourceId={sourceDialog?.id ?? null}
            />

            {/* Always-mounted edit dialogs — the `open` prop alone toggles
                visibility so Radix can run its close animation + body cleanup
                instead of being yanked out of the tree mid-close. */}
            <RecoveryEditDialog
                open={editDialog?.source === 'recovery'}
                onOpenChange={(v) => !v && setEditDialog(null)}
                recoveryId={editDialog?.source === 'recovery' ? editDialog.id : 0}
            />
            <ExpenseEditDialog
                open={editDialog?.source === 'expense'}
                onOpenChange={(v) => !v && setEditDialog(null)}
                expenseId={editDialog?.source === 'expense' ? editDialog.id : 0}
            />

            <AlertDialog open={deleteDialog !== null} onOpenChange={(v) => !v && setDeleteDialog(null)}>
                <AlertDialogContent>
                    <AlertDialogHeader>
                        <AlertDialogTitle className="flex items-center gap-2 text-red-700">
                            <AlertTriangle className="h-5 w-5" />
                            {deleteDialog?.source === 'recovery' && `Supprimer le recouvrement #${deleteDialog.id} ?`}
                            {deleteDialog?.source === 'expense' && `Supprimer la dépense #${deleteDialog.id} ?`}
                        </AlertDialogTitle>
                        <AlertDialogDescription>
                            {deleteDialog?.source === 'recovery' && (
                                <>
                                    Cette action supprime le recouvrement définitivement et retire la ligne
                                    correspondante de la caisse. Si un reçu signé est attaché, vous devrez d'abord
                                    le détacher.
                                </>
                            )}
                            {deleteDialog?.source === 'expense' && (
                                <>
                                    Cette action supprime la dépense (soft-delete) et retire la ligne correspondante
                                    de la caisse. Elle pourra être restaurée depuis la page Dépenses.
                                </>
                            )}
                        </AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                        <AlertDialogCancel>Annuler</AlertDialogCancel>
                        <AlertDialogAction
                            className="bg-red-700 text-white hover:bg-red-800"
                            onClick={() => {
                                if (!deleteDialog) return;
                                const { source, id } = deleteDialog;
                                const url = source === 'recovery'
                                    ? `/paiements/lignes/${id}`
                                    : `/depenses/delete/${id}`;
                                router.delete(url, {
                                    preserveScroll: true,
                                    only: ['rows', 'summary', 'flash'],
                                    onSuccess: () => {
                                        toast.success(
                                            source === 'recovery'
                                                ? 'Recouvrement supprimé'
                                                : 'Dépense supprimée',
                                        );
                                        setDeleteDialog(null);
                                    },
                                });
                            }}
                        >
                            Oui, supprimer
                        </AlertDialogAction>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialog>

            <AlertDialog open={resetOpen} onOpenChange={setResetOpen}>
                <AlertDialogContent>
                    <AlertDialogHeader>
                        <AlertDialogTitle className="flex items-center gap-2 text-red-700">
                            <AlertTriangle className="h-5 w-5" />
                            {t('fond_de_caisse', 'reset_dialog_title')}
                        </AlertDialogTitle>
                        <AlertDialogDescription>
                            {t('fond_de_caisse', 'reset_dialog_description')}
                        </AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                        <AlertDialogCancel>{t('fond_de_caisse', 'reset_cancel')}</AlertDialogCancel>
                        <AlertDialogAction
                            onClick={resetAll}
                            className="bg-red-700 text-white hover:bg-red-800"
                        >
                            {t('fond_de_caisse', 'reset_confirm')}
                        </AlertDialogAction>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialog>
        </AppLayout>
    );
}

interface SummaryCardProps {
    icon: React.ReactNode;
    label: string;
    value: string;
    accent: 'emerald' | 'red' | 'gray';
    emphasis?: boolean;
}

function SummaryCard({ icon, label, value, accent, emphasis }: SummaryCardProps) {
    const valueClass = accent === 'emerald' ? 'text-emerald-600' : accent === 'red' ? 'text-red-600' : '';
    return (
        <Card>
            <CardContent className="flex items-center justify-between px-4 py-3">
                <div className="flex flex-col gap-0.5">
                    <span className="text-muted-foreground text-xs">{label}</span>
                    <span className={`tabular-nums ${emphasis ? 'text-2xl font-bold' : 'text-xl font-semibold'} ${valueClass}`}>
                        {value}
                    </span>
                </div>
                {icon}
            </CardContent>
        </Card>
    );
}
