import {
    AlertDialog,
    AlertDialogAction,
    AlertDialogCancel,
    AlertDialogContent,
    AlertDialogDescription,
    AlertDialogFooter,
    AlertDialogHeader,
    AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Spinner } from '@/components/ui/spinner';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useTranslation } from '@/hooks/use-translation';
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, Link, router, usePage } from '@inertiajs/react';
import { AlertTriangle, CalendarPlus, ChevronDown, FileDown, IdCard, Plus, RefreshCcw, ShieldCheck, UserPlus, X } from 'lucide-react';
import * as React from 'react';
import { toast } from 'sonner';
import { OwnerFicheDialog } from '../fournisseurs/owner-fiche-dialog';
import { SupplierChoiceDialog, type ParcoursCreation } from '../fournisseurs/supplier-choice-dialog';
import { SupplierRegistreDialog } from '../fournisseurs/supplier-registre-dialog';
import { FiscalWarning } from './fiscal-warning';
import { moisEnAnneeMois } from './format';
import { LineDialog } from './line-dialog';
import { LinesTable } from './lines-table';
import { PaymentDialog } from './payment-dialog';
import { TejDepotDialog } from './tej-depot-dialog';
import { TotalsBar } from './totals-bar';
import type { RetenueLigne, RetenuePageProps, RetenueVerification, StatutPaiement, TypeBeneficiaire } from './types';
import { VerificationPanel } from './verification-panel';

/** Valeur du filtre « Type » quand il n'est pas posé. */
type FiltreBeneficiaire = TypeBeneficiaire | 'tous';

interface PageProps extends RetenuePageProps {
    flash?: { type: 'success' | 'error'; message: string };
    /**
     * Verrou serveur d'écriture TEJ et droit de création — la saisie d'une ligne
     * peut créer un fournisseur sans quitter son formulaire. Optionnels : leur
     * absence éteint le bouton d'ajout, ce qui est le défaut sûr.
     */
    peut_creer_fournisseur?: boolean;
    /**
     * Droit d'ouvrir « Fournisseurs & fiches fiscales » (`fournisseur.view`).
     * Son absence éteint le raccourci plutôt que d'offrir un lien vers un 403.
     */
    peut_voir_fournisseurs?: boolean;
    [key: string]: unknown;
}

/**
 * Filtre avec son « × » de retrait — patron repris de fond-de-caisse : l'utilisateur
 * enlève un filtre sans réinitialiser toute la barre.
 */
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 flex h-3.5 w-3.5 items-center justify-center rounded-full transition-colors hover:bg-red-100 hover:text-red-600"
                >
                    <X className="h-2.5 w-2.5" />
                </button>
            )}
        </div>
    );
}

export default function Index() {
    const { t } = useTranslation();
    const {
        declaration,
        lignesProprietaires,
        lignesFournisseurs,
        totaux,
        taux,
        operationsTej,
        fournisseurs,
        proprietaires,
        filters,
        avertissements,
        can,
        flash,
        verification,
        peut_creer_fournisseur,
        peut_voir_fournisseurs,
    } = usePage<PageProps>().props;

    const breadcrumbs: BreadcrumbItem[] = [{ title: t('retenue', 'title'), href: '/retenue-source' }];

    // ── Filtres ──────────────────────────────────────────────────────────────
    // Initialisés depuis la prop `filters` pour survivre au retour navigateur
    // (Inertia restaure l'URL + les props ; sans ça la barre se réinitialise).
    const [mois, setMois] = React.useState(filters.mois);
    const [search, setSearch] = React.useState(filters.search ?? '');
    const [statutPaiement, setStatutPaiement] = React.useState<StatutPaiement>(filters.statut_paiement ?? 'tous');
    const [beneficiaire, setBeneficiaire] = React.useState<FiltreBeneficiaire>(filters.beneficiaire ?? 'tous');

    // Garde de premier rendu : sans elle, l'effet rejouerait la requête au
    // montage et écraserait les filtres restaurés par le navigateur.
    const hasMounted = React.useRef(false);
    React.useEffect(() => {
        if (!hasMounted.current) {
            hasMounted.current = true;
            return;
        }
        const timer = setTimeout(() => {
            router.get(
                route('retenue.index'),
                {
                    mois,
                    search: search || undefined,
                    statut_paiement: statutPaiement !== 'tous' ? statutPaiement : undefined,
                    beneficiaire: beneficiaire !== 'tous' ? beneficiaire : undefined,
                },
                { preserveState: true, preserveScroll: true, replace: true },
            );
        }, 350);
        return () => clearTimeout(timer);
    }, [mois, search, statutPaiement, beneficiaire]);

    // ── Dialogues (montés en permanence, pilotés par `open`) ─────────────────
    const [lineDialog, setLineDialog] = React.useState<{ ligne: RetenueLigne | null } | null>(null);

    /**
     * « Corriger cette ligne », depuis le bandeau des lignes non déposables.
     *
     * ⚠️ LE BANDEAU N'EST PAS FILTRÉ, LES LISTES LE SONT. C'est voulu des deux
     * côtés : un filtre ne doit pas masquer ce qui bloquera le dépôt, mais il
     * masque bel et bien la ligne dans le tableau. La ligne visée peut donc être
     * ABSENTE des props — le piège déjà documenté sur `ouvrirDepotPourLigne`,
     * où un `find()` naïf ne faisait rien, en silence.
     *
     * On retombe alors sur le seul geste qui marche : effacer les filtres, et
     * ouvrir le formulaire dès que la ligne réapparaît dans les props.
     */
    const ligneAOuvrir = React.useRef<number | null>(null);

    const corrigerLigne = (ligneId: number) => {
        const visible = [...lignesProprietaires, ...lignesFournisseurs].find((l) => l.id === ligneId);

        if (visible) {
            setLineDialog({ ligne: visible });

            return;
        }

        ligneAOuvrir.current = ligneId;
        setSearch('');
        setStatutPaiement('tous');
        setBeneficiaire('tous');
    };

    React.useEffect(() => {
        if (ligneAOuvrir.current === null) return;

        const ligne = [...lignesProprietaires, ...lignesFournisseurs].find((l) => l.id === ligneAOuvrir.current);

        if (ligne) {
            ligneAOuvrir.current = null;
            setLineDialog({ ligne });
        }
    }, [lignesProprietaires, lignesFournisseurs]);
    const [paymentLigne, setPaymentLigne] = React.useState<RetenueLigne | null>(null);
    const [deleteLigne, setDeleteLigne] = React.useState<RetenueLigne | null>(null);
    const [verifOpen, setVerifOpen] = React.useState(false);
    const [verifLoading, setVerifLoading] = React.useState(false);
    const [depotTejOpen, setDepotTejOpen] = React.useState(false);
    /**
     * Ligne visée par le dépôt, ou `null` pour toute la période.
     *
     * `{ id, nom }` et non une `RetenueLigne` entière : c'est tout ce que
     * `TejDepotDialog` lit, et surtout c'est tout ce que le raccourci de la
     * saisie peut fournir — il reçoit ces deux valeurs du flash, sans pouvoir
     * les retrouver dans les props (voir `ouvrirDepotPourLigne`).
     */
    const [depotTejLigne, setDepotTejLigne] = React.useState<{ id: number; nom: string } | null>(null);

    /*
     * ═════════════════════════════════════════════════════════════════════════
     * CRÉER UN BÉNÉFICIAIRE SANS QUITTER LA DÉCLARATION
     * ═════════════════════════════════════════════════════════════════════════
     * Le geste existait, mais enterré à quatre niveaux : « Ajouter une ligne » →
     * type « Fournisseur » → ouvrir la liste → descendre à son PIED. Et cette
     * porte-là ne vaut que pour un fournisseur — un propriétaire n'en avait
     * aucune. Signalé tel quel : « où est le bouton […] je veux qu'il soit bien
     * visible ».
     *
     * ⚠️ CE SONT LES DIALOGUES DE /fournisseurs, PAS DES COPIES. Un second
     * parcours de création aurait divergé du premier au premier changement de
     * règle fiscale — `line-dialog` a déjà pris cette précaution pour le
     * fournisseur, on la tient pour les deux.
     *
     * `choixBeneficiaire` porte l'étape 1 (d'où vient l'identité ?),
     * `creationBeneficiaire` le formulaire qui en découle. Jamais les deux
     * ensemble : le dialogue de choix se ferme avant que l'autre s'ouvre.
     */
    const [choixBeneficiaire, setChoixBeneficiaire] = React.useState(false);
    const [creationBeneficiaire, setCreationBeneficiaire] = React.useState<ParcoursCreation | null>(null);

    const anyOpen =
        lineDialog !== null ||
        paymentLigne !== null ||
        deleteLigne !== null ||
        verifOpen ||
        depotTejOpen ||
        choixBeneficiaire ||
        creationBeneficiaire !== null;

    // Défensif : quand TOUS les dialogues sont refermés, s'assurer qu'aucun
    // reliquat Radix ne laisse `pointer-events:none` sur <body> (fuite connue
    // avec des dialogues empilés / montés conditionnellement).
    React.useEffect(() => {
        if (anyOpen) return;
        const timer = setTimeout(() => {
            if (typeof document !== 'undefined') {
                document.body.style.pointerEvents = '';
                document.body.style.removeProperty('pointer-events');
            }
        }, 300);
        return () => clearTimeout(timer);
    }, [anyOpen]);

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

    // ── Contrôle « Vérifier » ────────────────────────────────────────────────
    // `verification` est une prop Inertia « optional » : absente de tout rendu
    // qui ne la demande pas, elle disparaît des props dès la visite suivante
    // (l'ajout d'une ligne, par exemple). On en garde donc une copie locale pour
    // que le panneau reste lisible entre deux contrôles.
    const [verifRapport, setVerifRapport] = React.useState<RetenueVerification | null>(null);
    React.useEffect(() => {
        if (verification) setVerifRapport(verification);
    }, [verification]);

    /**
     * Raccourci de la dernière étape de `LineDialog` : ouvrir l'aperçu du dépôt
     * sur la ligne qui vient d'être créée.
     *
     * ⚠️ RIEN N'EST CHERCHÉ DANS LES PROPS, et c'est délibéré. Une première
     * version faisait `[...lignesProprietaires, ...lignesFournisseurs].find()`
     * en croyant ces listes brutes. Elles ne le sont pas :
     * `RetenuePageService::props()` ne publie que les lignes FILTRÉES. Créer une
     * ligne fournisseur avec le filtre « Propriétaire » posé — ou un terme de
     * recherche qui ne matche pas le nouveau bénéficiaire, ou « Statut = Payé »,
     * qu'une ligne neuve ne porte jamais — rendait la ligne introuvable, et le
     * raccourci ne faisait alors RIEN, en silence. Le menu de la ligne, lui
     * aussi masqué par le filtre, ne servait pas de repli.
     *
     * L'id ET le nom viennent donc du flash de `storeLigne()`, qui les connaît.
     */
    const ouvrirDepotPourLigne = (ligneId: number, nom: string) => {
        setDepotTejLigne({ id: ligneId, nom });
        setDepotTejOpen(true);
    };

    const lancerVerification = () => {
        router.reload({
            only: ['verification'],
            data: { verifier: 1 },
            onStart: () => setVerifLoading(true),
            onFinish: () => {
                setVerifLoading(false);
                setVerifOpen(true);
            },
        });
    };

    // ── Dérivés ──────────────────────────────────────────────────────────────
    const locked = declaration?.is_locked ?? false;

    /**
     * Combien de lignes n'ont pas encore de certificat chez la DGI.
     *
     * ⚠️ C'est LUI qui décide du bouton « Déposer », et plus `is_declaree`. Le
     * statut ne se recalcule jamais : une ligne ajoutée à une période déposée —
     * ce que le serveur autorise depuis le 2026-08-26 — laissait l'étiquette à
     * « déclarée » et le bouton caché. La ligne neuve n'aurait jamais pu partir.
     */
    const resteADeposer = declaration?.reste_a_deposer ?? 0;

    /**
     * POURQUOI on ne peut pas ajouter de ligne — `null` quand on peut.
     *
     * ⚠️ IL NE RESTE QUE LE DROIT D'ÉCRITURE. Le statut de la période
     * n'intervient plus : une déclaration clôturée ou déposée accepte de
     * nouvelles lignes, à la demande explicite de l'utilisateur — « je ne veux
     * pas de limitation ni de verrou […] je veux pouvoir ajouter autant de fois
     * que je le veux ». Le verrou serveur a suivi : il porte désormais sur la
     * LIGNE (`tej_uuid`) et non sur le mois. Ce qui est déjà chez la DGI ne se
     * réécrit pas en silence ; tout le reste est libre.
     */
    const raisonAjoutImpossible: string | null = !can.update
        ? 'Votre compte n’a pas le droit de modifier une déclaration de retenue à la source.'
        : null;

    // Les taux proposés valent pour un propriétaire comme pour un fournisseur.
    // Ils viennent de la configuration : aucun catalogue à filtrer.

    // Un seul tableau : les deux familles côte à côte, restreintes au filtre.
    const lignes = React.useMemo(() => {
        if (beneficiaire === 'proprietaire') return lignesProprietaires;
        if (beneficiaire === 'fournisseur') return lignesFournisseurs;
        return [...lignesProprietaires, ...lignesFournisseurs];
    }, [beneficiaire, lignesProprietaires, lignesFournisseurs]);

    // Totaux serveur correspondant exactement à l'ensemble affiché.
    const totauxAffiches =
        beneficiaire === 'proprietaire' ? totaux.proprietaires : beneficiaire === 'fournisseur' ? totaux.fournisseurs : totaux.global;

    const exportParams = () => {
        const params = new URLSearchParams();
        if (search) params.set('search', search);
        if (statutPaiement !== 'tous') params.set('statut_paiement', statutPaiement);
        if (beneficiaire !== 'tous') params.set('beneficiaire', beneficiaire);
        return params;
    };
    const exportPdf = () => {
        if (!declaration) return;
        window.open(`${route('retenue.export.pdf', { id: declaration.id })}?${exportParams().toString()}`, '_blank');
    };
    const exportExcel = () => {
        if (!declaration) return;
        window.location.href = `${route('retenue.export.excel', { id: declaration.id })}?${exportParams().toString()}`;
    };

    const ouvrirLeMois = () => {
        const { annee, mois: moisNumero } = moisEnAnneeMois(mois);
        router.post(route('retenue.declarations.store'), { annee, mois: moisNumero }, { preserveScroll: true });
    };

    const resetFilters = () => {
        setSearch('');
        setStatutPaiement('tous');
        setBeneficiaire('tous');
    };

    const statutVariant = declaration?.statut === 'declaree' ? 'default' : declaration?.statut === 'cloturee' ? 'secondary' : 'outline';

    const dateCourte = (iso: string | null) => (iso ? new Date(iso).toLocaleDateString('fr-FR') : '');

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

            <div className="flex h-full flex-1 flex-col gap-4 p-3 sm:p-4 lg:p-6">
                {/* En-tête + barre d'actions */}
                <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
                    <div>
                        <h1 className="text-2xl font-semibold tracking-tight">{t('retenue', 'title')}</h1>
                        <p className="text-muted-foreground text-sm">{t('retenue', 'subtitle')}</p>
                    </div>
                    <div className="flex flex-wrap items-center gap-2">
                        {/* Le contrôle reste offert sur une déclaration clôturée :
                            en lecture seule, il sert à justifier après coup. */}
                        {declaration && (
                            <Button onClick={lancerVerification} disabled={verifLoading}>
                                {verifLoading ? <Spinner size="sm" className="mr-2 text-current" /> : <ShieldCheck className="mr-2 h-4 w-4" />}
                                {verifLoading ? t('retenue', 'verify_running') : t('retenue', 'verify')}
                            </Button>
                        )}
                        {/* ═══════════════════════════════════════════════════
                            UN BOUTON QUI DISPARAÎT N'EXPLIQUE RIEN
                            ═══════════════════════════════════════════════════
                            Celui-ci s'effaçait dès qu'une des trois conditions
                            manquait — droit d'écriture, période ouverte,
                            déclaration non verrouillée — sans jamais dire
                            LAQUELLE. Signalé tel quel : « c'est normal que le
                            bouton ajouter ligne n'est pas dispo ? »
                            Impossible d'y répondre depuis l'écran.
                            Il reste donc VISIBLE, éteint, et il dit pourquoi. */}
                        {declaration && (
                            <Tooltip>
                                <TooltipTrigger asChild>
                                    {/* `span` intermédiaire : un bouton désactivé
                                        n'émet pas d'événement de survol, donc
                                        n'ouvre aucune infobulle. */}
                                    <span className={raisonAjoutImpossible ? 'cursor-not-allowed' : undefined}>
                                        <Button
                                            variant="outline"
                                            disabled={raisonAjoutImpossible !== null}
                                            onClick={() => setLineDialog({ ligne: null })}
                                        >
                                            <Plus className="mr-2 h-4 w-4" />
                                            {t('retenue', 'add_line')}
                                        </Button>
                                    </span>
                                </TooltipTrigger>
                                {raisonAjoutImpossible !== null && <TooltipContent>{raisonAjoutImpossible}</TooltipContent>}
                            </Tooltip>
                        )}
                        {/* ═══════════════════════════════════════════════════
                            LE SECOND GESTE DE CRÉATION, À CÔTÉ DU PREMIER
                            ═══════════════════════════════════════════════════
                            Il n'existait que caché au PIED de la liste
                            déroulante de « Ajouter une ligne », et seulement
                            pour un fournisseur — un propriétaire n'avait aucune
                            porte. Les deux « Ajouter » vivent désormais côte à
                            côte : une ligne se rattache à un bénéficiaire, on
                            crée donc l'un depuis là où l'on saisit l'autre.

                            PAS de garde sur `declaration` : une fiche ne dépend
                            d'aucun mois, et un mois pas encore ouvert est
                            justement quand on les prépare. */}
                        {peut_creer_fournisseur && (
                            <Button variant="outline" onClick={() => setChoixBeneficiaire(true)}>
                                <UserPlus className="mr-2 h-4 w-4" />
                                {t('retenue', 'ajouter_beneficiaire', 'Ajouter un bénéficiaire')}
                            </Button>
                        )}
                        {/* ═══════════════════════════════════════════════════
                            LES FICHES FISCALES SE RÉPARENT D'ICI
                            ═══════════════════════════════════════════════════
                            C'est cet écran qui refuse un dépôt pour une adresse
                            ou une date de naissance manquante ; c'est donc d'ici
                            qu'on doit pouvoir aller les saisir. Le bandeau
                            d'avertissement y renvoie déjà ligne par ligne — ce
                            bouton ouvre le même écran sans partir d'un refus. */}
                        {peut_voir_fournisseurs && (
                            <Button variant="outline" asChild>
                                <Link href="/fournisseurs">
                                    <IdCard className="mr-2 h-4 w-4" />
                                    {t('retenue', 'fiches_fiscales', 'Fournisseurs & fiches fiscales')}
                                </Link>
                            </Button>
                        )}
                        {/* Les deux exports sous un seul bouton : ce sont deux
                            formats du MÊME contenu, et à plat ils occupaient
                            deux places dans une barre qui en manquait. */}
                        {declaration && (
                            <DropdownMenu>
                                <DropdownMenuTrigger asChild>
                                    <Button variant="outline">
                                        <FileDown className="mr-2 h-4 w-4" />
                                        {t('retenue', 'exporter', 'Exporter')}
                                        <ChevronDown className="ml-2 h-4 w-4" />
                                    </Button>
                                </DropdownMenuTrigger>
                                <DropdownMenuContent align="end">
                                    <DropdownMenuItem onSelect={exportPdf}>
                                        <FileDown className="mr-2 h-4 w-4" />
                                        {t('retenue', 'export_pdf')}
                                    </DropdownMenuItem>
                                    <DropdownMenuItem onSelect={exportExcel}>
                                        <FileDown className="mr-2 h-4 w-4" />
                                        {t('retenue', 'export_excel')}
                                    </DropdownMenuItem>
                                </DropdownMenuContent>
                            </DropdownMenu>
                        )}
                        {/* ═══════════════════════════════════════════════════
                            PAS DE « DÉPOSER SUR TEJ » POUR TOUTE LA PÉRIODE
                            ═══════════════════════════════════════════════════
                            Retiré le 2026-08-27, à la demande de l'utilisateur :
                            « je déposerai à TEJ un par un ». C'est aussi la
                            granularité du portail de la DGI, où chaque
                            validation de formulaire dépose UN certificat.

                            Le dépôt se fait donc depuis le menu de la ligne
                            (« Envoyer à TEJ… »), qui ouvre le même récapitulatif
                            sur un seul certificat — plus lisible qu'un fichier
                            de trente, et sans le risque d'en emporter un qu'on
                            n'avait pas relu.

                            ⚠️ La route de période reste EN PLACE et testée : la
                            commande `retenue:deposer` s'en sert, et c'est le
                            seul chemin pour un mois entier quand il le faut. */}
                    </div>
                </div>

                {/* Filtres */}
                <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">
                            <Label className="text-xs">{t('retenue', 'period')}</Label>
                            <Input type="month" value={mois} onChange={(e) => setMois(e.target.value)} className="h-9 w-[180px] tabular-nums" />
                        </div>

                        <div className="flex flex-col gap-1">
                            <FilterLabel active={beneficiaire !== 'tous'} onClear={() => setBeneficiaire('tous')}>
                                {t('retenue', 'beneficiary_type')}
                            </FilterLabel>
                            <Select value={beneficiaire} onValueChange={(value) => setBeneficiaire(value as FiltreBeneficiaire)}>
                                <SelectTrigger className="h-9 w-[170px]">
                                    <SelectValue />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectItem value="tous">{t('retenue', 'filter_all')}</SelectItem>
                                    <SelectItem value="proprietaire">{t('retenue', 'tab_owners')}</SelectItem>
                                    <SelectItem value="fournisseur">{t('retenue', 'tab_suppliers')}</SelectItem>
                                </SelectContent>
                            </Select>
                        </div>

                        <div className="flex flex-col gap-1">
                            <FilterLabel active={statutPaiement !== 'tous'} onClear={() => setStatutPaiement('tous')}>
                                {t('retenue', 'filter_payment_status')}
                            </FilterLabel>
                            <Select value={statutPaiement} onValueChange={(value) => setStatutPaiement(value as StatutPaiement)}>
                                <SelectTrigger className="h-9 w-[170px]">
                                    <SelectValue />
                                </SelectTrigger>
                                <SelectContent>
                                    <SelectItem value="tous">{t('retenue', 'filter_all')}</SelectItem>
                                    <SelectItem value="paye">{t('retenue', 'paid')}</SelectItem>
                                    <SelectItem value="non_paye">{t('retenue', 'unpaid')}</SelectItem>
                                </SelectContent>
                            </Select>
                        </div>

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

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

                {declaration === null ? (
                    /* Mois pas encore ouvert */
                    <Card>
                        <CardContent className="flex flex-col items-center gap-3 px-4 py-12 text-center">
                            <CalendarPlus className="text-muted-foreground h-10 w-10" />
                            <p className="text-muted-foreground text-sm">{t('retenue', 'no_declaration')}</p>
                            {can.update && (
                                <Button onClick={ouvrirLeMois}>
                                    <CalendarPlus className="mr-2 h-4 w-4" />
                                    {t('retenue', 'create_declaration')}
                                </Button>
                            )}
                        </CardContent>
                    </Card>
                ) : (
                    <>
                        {/* Statut de la déclaration */}
                        <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm">
                            <span className="font-semibold">{declaration.libelle_periode}</span>
                            <Badge variant={statutVariant}>{t('retenue', `statut_${declaration.statut}`)}</Badge>
                            {declaration.cloturee_at && (
                                <span className="text-muted-foreground text-xs">
                                    {t('retenue', 'closed_at').replace(':date', dateCourte(declaration.cloturee_at))}
                                </span>
                            )}
                            {declaration.declaree_at && (
                                <span className="text-muted-foreground text-xs">
                                    {t('retenue', 'declared_at').replace(':date', dateCourte(declaration.declaree_at))}
                                </span>
                            )}
                            {declaration.tej_reference && (
                                <span className="text-muted-foreground text-xs">
                                    {t('retenue', 'tej_reference')} : {declaration.tej_reference}
                                </span>
                            )}
                        </div>

                        {/* Bandeaux de verrouillage */}
                        {/* Les bandeaux « Déclaration déposée : plus aucune
                            modification n'est possible » et « Déclaration
                            clôturée : les montants ne sont plus modifiables »
                            sont RETIRÉS : ils décrivaient un verrou de période
                            qui n'existe plus. Une affirmation fausse à l'écran
                            coûte plus cher qu'une information absente. La date
                            de dépôt, elle, reste affichée juste au-dessus — un
                            fait, pas une règle. */}

                        <FiscalWarning lignes={avertissements.lignes_non_deposables} onCorrigerLigne={corrigerLigne} />

                        {avertissements.logements_sans_proprietaire > 0 && (
                            <div className="flex items-center gap-2 rounded-md border border-amber-300 bg-amber-50 px-4 py-2 text-sm text-amber-900">
                                <AlertTriangle className="h-4 w-4 shrink-0" />
                                {t('retenue', 'warning_properties_without_owner').replace(
                                    ':count',
                                    String(avertissements.logements_sans_proprietaire),
                                )}
                            </div>
                        )}

                        <TotalsBar totaux={totauxAffiches} />

                        <Card className="flex-1 overflow-hidden">
                            <CardContent className="p-0">
                                <LinesTable
                                    lignes={lignes}
                                    totaux={totauxAffiches}
                                    declarationId={declaration.id}
                                    taux={taux}
                                    locked={locked}
                                    can={can}
                                    onEdit={(ligne) => setLineDialog({ ligne })}
                                    onPayment={setPaymentLigne}
                                    onDelete={setDeleteLigne}
                                    onDeposerTej={(ligne) => {
                                        setDepotTejLigne({ id: ligne.id, nom: ligne.beneficiaire_nom });
                                        setDepotTejOpen(true);
                                    }}
                                />
                            </CardContent>
                        </Card>
                    </>
                )}
            </div>

            {/* Tous les dialogues restent montés : seule la prop `open` bascule,
                pour laisser Radix jouer son animation de fermeture et nettoyer
                le body-lock au lieu d'être arraché de l'arbre en plein vol. */}
            <LineDialog
                open={lineDialog !== null}
                onOpenChange={(value) => !value && setLineDialog(null)}
                declarationId={declaration?.id ?? 0}
                ligne={lineDialog?.ligne ?? null}
                taux={taux}
                operationsTej={operationsTej}
                proprietaires={proprietaires}
                fournisseurs={fournisseurs}
                peutCreerFournisseur={peut_creer_fournisseur === true}
                peutDeposer={can.declare && resteADeposer > 0}
                onDeposerTej={ouvrirDepotPourLigne}
            />

            {/* ═════════════════════════════════════════════════════════════
                CRÉATION D'UN BÉNÉFICIAIRE — les dialogues de /fournisseurs
                ═════════════════════════════════════════════════════════════
                Étape 1, le choix de la pièce d'identité ; étape 2, le
                formulaire qui en découle. Ce découpage n'est pas un goût
                d'écran : c'est celui du portail TEJ, où un matricule fiscal
                auto-remplit tout et où un CIN exige adresse, téléphone ET date
                de naissance. Voir l'en-tête de `supplier-choice-dialog`.

                ⚠️ `onEnregistre` EST INDISPENSABLE ICI. Sans lui,
                `OwnerFicheDialog` termine par un `router.get('/fournisseurs')`
                — nécessaire sur cet écran-là, qui doit lâcher ses filtres pour
                montrer la fiche neuve, mais une ÉJECTION ici. Les deux routes
                de création répondent `back()` : les props de cette page se
                rafraîchissent seules, la fiche apparaît dans les listes, et il
                n'y a rien à recharger. */}
            <SupplierChoiceDialog
                open={choixBeneficiaire}
                onOpenChange={(value) => !value && setChoixBeneficiaire(false)}
                onChoisir={(parcours) => {
                    setChoixBeneficiaire(false);
                    setCreationBeneficiaire(parcours);
                }}
            />

            <SupplierRegistreDialog open={creationBeneficiaire === 'matricule'} onOpenChange={(value) => !value && setCreationBeneficiaire(null)} />

            <OwnerFicheDialog
                open={creationBeneficiaire === 'proprietaire'}
                onOpenChange={(value) => !value && setCreationBeneficiaire(null)}
                proprietaires={proprietaires}
                onEnregistre={() => setCreationBeneficiaire(null)}
            />

            <PaymentDialog
                open={paymentLigne !== null}
                onOpenChange={(value) => !value && setPaymentLigne(null)}
                declarationId={declaration?.id ?? 0}
                ligne={paymentLigne}
            />

            <VerificationPanel
                open={verifOpen}
                onOpenChange={setVerifOpen}
                declarationId={declaration?.id ?? 0}
                verification={verifRapport}
                canUpdate={can.update}
            />

            {declaration && (
                <TejDepotDialog
                    open={depotTejOpen}
                    onOpenChange={setDepotTejOpen}
                    declarationId={declaration.id}
                    libellePeriode={declaration.libelle_periode}
                    ligne={depotTejLigne}
                />
            )}

            <AlertDialog open={deleteLigne !== null} onOpenChange={(value) => !value && setDeleteLigne(null)}>
                <AlertDialogContent>
                    <AlertDialogHeader>
                        <AlertDialogTitle className="flex items-center gap-2 text-red-700">
                            <AlertTriangle className="h-5 w-5" />
                            {t('retenue', 'delete_line')}
                        </AlertDialogTitle>
                        <AlertDialogDescription>
                            {t('retenue', 'delete_line_confirm').replace(':beneficiaire', deleteLigne?.beneficiaire_nom ?? '')}
                        </AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                        <AlertDialogCancel>{t('retenue', 'cancel')}</AlertDialogCancel>
                        <AlertDialogAction
                            className="bg-red-700 text-white hover:bg-red-800"
                            onClick={() => {
                                if (!declaration || !deleteLigne) return;
                                router.delete(route('retenue.lignes.destroy', { id: declaration.id, ligne: deleteLigne.id }), {
                                    preserveScroll: true,
                                    onFinish: () => setDeleteLigne(null),
                                });
                            }}
                        >
                            {t('retenue', 'confirm')}
                        </AlertDialogAction>
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialog>
        </AppLayout>
    );
}
