import { toLocalIso } from '@/components/fr-date';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { useTranslation } from '@/hooks/use-translation';
import AppLayout from '@/layouts/app-layout';
import { cn } from '@/lib/utils';
import type { BreadcrumbItem } from '@/types';
import { Head, Link, router, usePage } from '@inertiajs/react';
import {
    BedDouble,
    ChevronDown,
    ChevronLeft,
    ChevronRight,
    FileDown,
    FilePlus2,
    FileSpreadsheet,
    FileText,
    FileUp,
    HandCoins,
    Hash,
    Landmark,
    Plus,
    ReceiptText,
    Search,
    X,
    type LucideIcon,
} from 'lucide-react';
import { useEffect, useMemo, useState, type MouseEvent, type ReactNode } from 'react';
import { toast } from 'sonner';
import { CommissionSheet } from './components/commission-sheet';
import { ExportMenu } from './components/export-menu';
import { ImportDialog } from './components/import-dialog';
import { NumberingDialog } from './components/numbering-dialog';
import { PeriodeBar } from './components/periode-bar';
import { RapportAirbnbSheet } from './components/rapport-airbnb-sheet';
import { ReleveBancaireSheet } from './components/releve-bancaire-sheet';
import { StatusChip } from './components/status-chip';
import { SuppressionBrouillons } from './components/suppression-brouillons';
import { fmtDate, fmtMontant, TYPE_LIBELLE } from './format';
import { plageImpayees, type ParamsPeriode } from './periode';
import type { FactureListe, FacturesIndexProps, FactureStatut, FactureType } from './types';

interface Flash {
    type: 'success' | 'error';
    message: string;
}

/** Onglets de statut — « Non payées » inclut les factures en retard. */
type Pastille = 'toutes' | 'brouillon' | 'non_payee' | 'en_retard' | 'payee' | 'annulee';
type FiltreType = 'tout' | FactureType;

const PASTILLES: Pastille[] = ['toutes', 'brouillon', 'non_payee', 'en_retard', 'payee', 'annulee'];
const FILTRES_TYPE: FiltreType[] = ['tout', 'facture', 'proforma', 'avoir'];
const PAR_PAGE = 25;

const DANS_PASTILLE: Record<Pastille, (f: FactureListe) => boolean> = {
    toutes: () => true,
    brouillon: (f) => f.statut === 'brouillon',
    // Une proforma ou un avoir émis ne se paient pas : seules les factures attendent un paiement.
    non_payee: (f) => f.type === 'facture' && f.statut === 'emise',
    en_retard: (f) => f.en_retard,
    payee: (f) => f.statut === 'payee',
    annulee: (f) => f.statut === 'annulee' || f.statut === 'convertie',
};

/** `?statut=brouillon` — posé par le serveur après la création de plusieurs brouillons. */
function pastilleDepuisUrl(url: string): Pastille {
    const statut = new URLSearchParams(url.split('?')[1] ?? '').get('statut');
    return PASTILLES.find((p) => p === statut) ?? 'toutes';
}

/** Onglet de type repris de l'adresse : « À encaisser » ouvre la plage des impayées sur les seules factures. */
function typeDepuisUrl(url: string): FiltreType {
    const type = new URLSearchParams(url.split('?')[1] ?? '').get('type');
    return FILTRES_TYPE.find((ft) => ft === type) ?? 'tout';
}

/** Recherche insensible à la casse et aux accents. */
function normaliser(texte: string): string {
    // NFD sépare chaque lettre de ses accents (marques combinantes, \p{M}), qu'on retire.
    return texte.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
}

/** Jours écoulés depuis une date ISO, en dates locales. */
function joursDepuis(iso: string): number {
    const [a, m, j] = iso.slice(0, 10).split('-').map(Number);
    const jour = new Date(a, m - 1, j);
    const auj = new Date();
    auj.setHours(0, 0, 0, 0);
    return Math.max(0, Math.round((auj.getTime() - jour.getTime()) / 86_400_000));
}

/** Remplace les `:cle` d'un libellé traduit. */
function remplir(texte: string, vars?: Record<string, string | number>): string {
    if (!vars) return texte;
    return Object.entries(vars).reduce((s, [cle, valeur]) => s.split(`:${cle}`).join(String(valeur)), texte);
}

export default function FacturesIndex({ factures, periode, repartition, kpis, prochains, planchers, config, can }: FacturesIndexProps) {
    const { props: partagees, url } = usePage<{ flash?: Flash | null }>();
    const flash = partagees.flash;
    const { t } = useTranslation();
    const tr = (cle: string, fr: string, vars?: Record<string, string | number>) => remplir(t('factures', cle, fr), vars);

    const [pastille, setPastille] = useState<Pastille>(() => pastilleDepuisUrl(url));
    const [filtreType, setFiltreType] = useState<FiltreType>(() => typeDepuisUrl(url));
    const [recherche, setRecherche] = useState('');
    const [pageCourante, setPageCourante] = useState(1);

    const [commissionOuverte, setCommissionOuverte] = useState(false);
    const [importOuvert, setImportOuvert] = useState(false);
    const [rapportOuvert, setRapportOuvert] = useState(false);
    const [releveOuvert, setReleveOuvert] = useState(false);
    const [numerotationOuverte, setNumerotationOuverte] = useState(false);
    // Brouillons cochés pour une suppression groupée (les pièces émises ne se cochent pas).
    const [selection, setSelection] = useState<Set<number>>(() => new Set());

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

    const breadcrumbs: BreadcrumbItem[] = [{ title: tr('liste.titre', 'Factures'), href: '/factures' }];

    // ── Filtres (côté navigateur : la période, elle, est chargée par le serveur) ──
    const terme = normaliser(recherche.trim());
    const avantPastille = useMemo(
        () =>
            factures.filter(
                (f) =>
                    (filtreType === 'tout' || f.type === filtreType) &&
                    (terme === '' || normaliser([f.numero, f.client_nom, f.origine_libelle].filter(Boolean).join(' ')).includes(terme)),
            ),
        [factures, filtreType, terme],
    );
    const comptes = useMemo(
        () => Object.fromEntries(PASTILLES.map((p) => [p, avantPastille.filter(DANS_PASTILLE[p]).length])) as Record<Pastille, number>,
        [avantPastille],
    );
    const visibles = useMemo(() => avantPastille.filter(DANS_PASTILLE[pastille]), [avantPastille, pastille]);

    const nbPages = Math.max(1, Math.ceil(visibles.length / PAR_PAGE));
    const pageAffichee = Math.min(pageCourante, nbPages);
    const tranche = visibles.slice((pageAffichee - 1) * PAR_PAGE, pageAffichee * PAR_PAGE);
    const filtresActifs = pastille !== 'toutes' || filtreType !== 'tout' || recherche !== '';

    // ── Sélection : seulement les brouillons de la page affichée ──
    // Changer de filtre, d'onglet ou de page vide la sélection : on ne supprime
    // jamais une ligne qu'on ne voit plus. La liste rechargée après une
    // suppression la vide aussi.
    useEffect(() => setSelection(new Set()), [pastille, filtreType, terme, pageAffichee, factures]);
    const brouillonsVisibles = tranche.filter((f) => f.statut === 'brouillon');
    const choisis = brouillonsVisibles.filter((f) => selection.has(f.id));
    const toutCoche = brouillonsVisibles.length > 0 && choisis.length === brouillonsVisibles.length;
    const basculer = (id: number) =>
        setSelection((prec) => {
            const suivante = new Set(prec);
            if (suivante.has(id)) suivante.delete(id);
            else suivante.add(id);
            return suivante;
        });
    const toutBasculer = () => setSelection(toutCoche ? new Set() : new Set(brouillonsVisibles.map((f) => f.id)));

    const choisirPastille = (p: Pastille) => {
        setPastille(p);
        setPageCourante(1);
    };
    const choisirType = (valeur: string) => {
        const suivant = FILTRES_TYPE.find((ft) => ft === valeur);
        if (!suivant) return; // ToggleGroup renvoie '' quand on reclique l'élément actif.
        setFiltreType(suivant);
        setPageCourante(1);
    };
    const chercher = (valeur: string) => {
        setRecherche(valeur);
        setPageCourante(1);
    };
    const reinitialiserFiltres = () => {
        setPastille('toutes');
        setFiltreType('tout');
        setRecherche('');
        setPageCourante(1);
    };
    // Nouvelle période : rechargée par le serveur (liste ET indicateurs), l'onglet de statut est gardé.
    const changerPeriode = (params: ParamsPeriode) => {
        router.get(
            route('factures.index'),
            { ...params, statut: pastille === 'toutes' ? undefined : pastille },
            { preserveState: false, preserveScroll: true },
        );
    };

    // « À encaisser » et « En retard » comptent toutes les périodes : si la période
    // affichée ne contient pas toutes les impayées, le clic ouvre leur plage.
    const voirImpayees = (cible: Pastille, type: FiltreType) => {
        const ouvrir = plageImpayees(kpis.impayees_depuis, periode, toLocalIso(new Date()));
        if (ouvrir) {
            router.get(
                route('factures.index'),
                { ...ouvrir, statut: cible, type: type === 'tout' ? undefined : type },
                { preserveState: false, preserveScroll: true },
            );
            return;
        }
        choisirPastille(cible);
        setFiltreType(type);
    };

    const ouvrirFacture = (event: MouseEvent, f: FactureListe) => {
        const url = route('factures.edit', f.id);
        if (event.metaKey || event.ctrlKey) window.open(url, '_blank', 'noopener');
        else router.visit(url);
    };

    const LIBELLE_PASTILLE: Record<Pastille, string> = {
        toutes: tr('liste.pastille_toutes', 'Toutes'),
        brouillon: tr('liste.pastille_brouillons', 'Brouillons'),
        non_payee: tr('liste.pastille_non_payees', 'Non payées'),
        en_retard: tr('liste.pastille_en_retard', 'En retard'),
        payee: tr('liste.pastille_payees', 'Payées'),
        annulee: tr('liste.pastille_annulees', 'Annulées'),
    };
    const LIBELLE_TYPE: Record<FiltreType, string> = {
        tout: tr('liste.type_tout', 'Tout'),
        facture: tr('liste.type_factures', 'Factures'),
        proforma: tr('liste.type_proformas', 'Proformas'),
        avoir: tr('liste.type_avoirs', 'Avoirs'),
    };
    const LEGENDE: { statut: FactureStatut; enRetard: boolean; texte: string }[] = [
        { statut: 'brouillon', enRetard: false, texte: tr('liste.legende_brouillon', 'pas encore de numéro') },
        { statut: 'emise', enRetard: false, texte: tr('liste.legende_non_payee', 'émise, en attente de paiement') },
        { statut: 'emise', enRetard: true, texte: tr('liste.legende_en_retard', 'échéance dépassée') },
        { statut: 'payee', enRetard: false, texte: tr('liste.legende_payee', 'encaissée') },
    ];

    const nouvelleFacture = () => router.visit(route('factures.create'));

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title={tr('liste.titre', 'Factures')} />
            <div className="flex h-full min-w-0 flex-1 flex-col gap-4 p-3 sm:p-4 lg:p-6">
                {/* En-tête */}
                <div className="flex w-full min-w-0 flex-wrap items-start justify-between gap-3">
                    <div className="min-w-0">
                        <h1 className="text-xl font-semibold">{tr('liste.titre', 'Factures')}</h1>
                        <p className="text-muted-foreground mt-0.5 text-sm">
                            {tr('liste.sous_titre', "Factures, proformas et avoirs — numérotés à l'émission, jamais supprimés une fois émis.")}
                        </p>
                    </div>
                    <div className="flex flex-wrap items-center gap-2">
                        {can.emettre && (
                            <Button
                                type="button"
                                variant="outline"
                                size="sm"
                                onClick={() => setNumerotationOuverte(true)}
                                title={tr('liste.numerotation_aide', 'Reprendre la numérotation')}
                                data-tour="factures-numbering"
                            >
                                <Hash className="text-muted-foreground size-3.5" />
                                <span className="text-muted-foreground">{tr('liste.prochaine', 'Prochaine :')}</span>
                                <span className="font-mono">{prochains.facture}</span>
                            </Button>
                        )}
                        {/* La vue affichée (période + onglet + type + recherche) en PDF ou Excel. */}
                        <ExportMenu vue={{ periode, statut: pastille, type: filtreType, recherche }} nombre={visibles.length} tr={tr} />
                        {can.create && (
                            <DropdownMenu modal={false}>
                                <DropdownMenuTrigger asChild>
                                    <Button className="bg-secondary hover:bg-secondary/90" data-tour="factures-new">
                                        <Plus className="size-4" />
                                        {tr('liste.nouvelle', 'Nouvelle facture')}
                                        <ChevronDown className="size-4 opacity-80" />
                                    </Button>
                                </DropdownMenuTrigger>
                                <DropdownMenuContent align="end" className="w-80">
                                    <ItemCreation
                                        icone={FilePlus2}
                                        titre={tr('liste.menu_vierge', 'Facture vierge')}
                                        description={tr('liste.menu_vierge_aide', 'Saisie libre, ligne par ligne')}
                                        onSelect={nouvelleFacture}
                                    />
                                    <ItemCreation
                                        icone={FileText}
                                        titre={tr('liste.menu_proforma', 'Proforma')}
                                        description={tr('liste.menu_proforma_aide', 'Devis chiffré sans valeur fiscale, convertible en facture')}
                                        onSelect={() => router.visit(route('factures.create', { type: 'proforma' }))}
                                    />
                                    <DropdownMenuSeparator />
                                    <ItemCreation
                                        icone={HandCoins}
                                        titre={tr('liste.menu_commission', 'Commission du mois')}
                                        description={tr('liste.menu_commission_aide', 'Une ligne par réservation, calculée par le Manager')}
                                        onSelect={() => setCommissionOuverte(true)}
                                    />
                                    <ItemCreation
                                        icone={FileSpreadsheet}
                                        titre={tr('liste.menu_rapport', 'Rapport de versement Airbnb')}
                                        description={tr(
                                            'liste.menu_rapport_aide',
                                            'Une ligne par séjour du rapport, selon la règle de paiement du logement',
                                        )}
                                        onSelect={() => setRapportOuvert(true)}
                                    />
                                    <ItemCreation
                                        icone={Landmark}
                                        titre={tr('liste.menu_releve', 'Relevé bancaire')}
                                        description={tr('liste.menu_releve_aide', 'Versements Airbnb du relevé, en dinars au taux BCT du jour')}
                                        onSelect={() => setReleveOuvert(true)}
                                    />
                                    <ItemCreation
                                        icone={BedDouble}
                                        titre={tr('liste.menu_reservation', 'Depuis une réservation')}
                                        description={tr('liste.menu_reservation_aide', "Menu « … » d'une réservation → Créer une facture")}
                                        onSelect={() =>
                                            router.visit(route('reservations'), {
                                                onSuccess: () =>
                                                    toast.info(
                                                        tr(
                                                            'liste.menu_reservation_toast',
                                                            "Ouvrez le menu « … » d'une réservation, puis choisissez « Créer une facture ».",
                                                        ),
                                                    ),
                                            })
                                        }
                                    />
                                    <DropdownMenuSeparator />
                                    <ItemCreation
                                        icone={FileUp}
                                        titre={tr('liste.menu_import', 'Importer un PDF')}
                                        description={
                                            config.import_actif
                                                ? tr('liste.menu_import_aide', 'Lecture ligne par ligne, puis brouillon à relire')
                                                : tr('liste.menu_import_indisponible', 'Lecture automatique indisponible (clé OpenAI absente)')
                                        }
                                        disabled={!config.import_actif}
                                        onSelect={() => setImportOuvert(true)}
                                    />
                                </DropdownMenuContent>
                            </DropdownMenu>
                        )}
                    </div>
                </div>

                {/* Période — la liste ET les indicateurs la suivent */}
                <PeriodeBar periode={periode} repartition={repartition} onChange={changerPeriode} />

                {/* Indicateurs — « À encaisser » et « En retard » : toutes périodes ; les deux autres : la période. Un clic filtre la liste. */}
                <div className="grid w-full min-w-0 grid-cols-2 gap-3 sm:gap-4 xl:grid-cols-4" data-tour="factures-kpis">
                    <KpiCard
                        titre={tr('liste.kpi_a_encaisser', 'À encaisser')}
                        accent="amber"
                        montant
                        valeur={fmtMontant(kpis.a_encaisser.TND ?? 0, 'TND')}
                        detail={`${fmtMontant(kpis.a_encaisser.EUR ?? 0, 'EUR')} · ${fmtMontant(kpis.a_encaisser.USD ?? 0, 'USD')}`}
                        sousTitre={tr('liste.kpi_a_encaisser_aide', 'factures émises non payées')}
                        actif={pastille === 'non_payee' && filtreType === 'facture'}
                        // Le montant ne compte que les FACTURES émises : la liste suit.
                        onClick={() => voirImpayees('non_payee', 'facture')}
                    />
                    <KpiCard
                        titre={tr('liste.kpi_en_retard', 'En retard')}
                        accent="rose"
                        valeur={kpis.en_retard}
                        sousTitre={tr('liste.kpi_en_retard_aide', 'échéance dépassée')}
                        actif={pastille === 'en_retard'}
                        onClick={() => voirImpayees('en_retard', 'tout')}
                    />
                    <KpiCard
                        titre={tr('liste.kpi_brouillons', 'Brouillons')}
                        accent="slate"
                        valeur={kpis.brouillons}
                        sousTitre={tr('liste.kpi_brouillons_aide', 'sans numéro, modifiables')}
                        actif={pastille === 'brouillon'}
                        onClick={() => choisirPastille('brouillon')}
                    />
                    {/* Chiffre de la période pour la finance : ne filtre rien, il additionne. */}
                    <KpiCard
                        titre={tr('liste.kpi_facture', 'Facturé HT')}
                        accent="sky"
                        montant
                        valeur={fmtMontant(kpis.facture_ht.TND ?? 0, 'TND')}
                        detail={`${fmtMontant(kpis.facture_ht.EUR ?? 0, 'EUR')} · ${fmtMontant(kpis.facture_ht.USD ?? 0, 'USD')}`}
                        sousTitre={tr('liste.kpi_facture_aide', ':n facture(s) émise(s), avoirs déduits', { n: kpis.nb_emises })}
                    />
                </div>

                {/* Onglets de statut + filtres */}
                <div className="flex w-full min-w-0 flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
                    <div className="flex flex-wrap items-center gap-2" data-tour="factures-tabs">
                        {PASTILLES.map((p) => {
                            const actif = pastille === p;
                            return (
                                <button
                                    key={p}
                                    type="button"
                                    aria-pressed={actif}
                                    onClick={() => choisirPastille(p)}
                                    className={cn(
                                        'inline-flex items-center gap-1.5 rounded-full border px-3.5 py-1.5 text-sm font-medium transition',
                                        actif
                                            ? 'border-secondary bg-secondary text-white shadow-sm'
                                            : 'border-border text-muted-foreground hover:border-secondary/40 hover:text-foreground bg-white',
                                    )}
                                >
                                    {LIBELLE_PASTILLE[p]}
                                    <span
                                        className={cn(
                                            'min-w-5 rounded-full px-1.5 text-center text-[11px] leading-5 font-semibold tabular-nums',
                                            actif ? 'bg-white/20 text-white' : 'bg-muted text-muted-foreground',
                                        )}
                                    >
                                        {comptes[p]}
                                    </span>
                                </button>
                            );
                        })}
                    </div>

                    <div className="flex flex-wrap items-center gap-2" data-tour="factures-filters">
                        <ToggleGroup
                            type="single"
                            variant="outline"
                            value={filtreType}
                            onValueChange={choisirType}
                            className="bg-white"
                            aria-label={tr('liste.filtre_type', 'Type de pièce')}
                        >
                            {FILTRES_TYPE.map((ft) => (
                                <ToggleGroupItem
                                    key={ft}
                                    value={ft}
                                    className="text-muted-foreground data-[state=on]:bg-secondary/10 data-[state=on]:text-primary px-3 data-[state=on]:font-semibold"
                                >
                                    {LIBELLE_TYPE[ft]}
                                </ToggleGroupItem>
                            ))}
                        </ToggleGroup>
                        <div className="relative w-full sm:w-64">
                            <Search className="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" />
                            <Input
                                value={recherche}
                                onChange={(e) => chercher(e.target.value)}
                                placeholder={tr('liste.recherche', 'Numéro, client, origine…')}
                                className="bg-white pr-8 pl-8"
                                aria-label={tr('liste.recherche_aide', 'Rechercher une facture')}
                            />
                            {recherche !== '' && (
                                <button
                                    type="button"
                                    onClick={() => chercher('')}
                                    className="text-muted-foreground hover:bg-muted hover:text-foreground absolute top-1/2 right-2 inline-flex size-5 -translate-y-1/2 items-center justify-center rounded-full"
                                    aria-label={tr('liste.recherche_effacer', 'Effacer la recherche')}
                                >
                                    <X className="size-3.5" />
                                </button>
                            )}
                        </div>
                    </div>
                </div>

                {/* Liste */}
                {can.delete && <SuppressionBrouillons choisis={choisis} onVider={() => setSelection(new Set())} />}

                <div data-tour="factures-table" className="w-full min-w-0">
                    {visibles.length === 0 ? (
                        <div className="flex flex-col items-center gap-3 rounded-xl border border-dashed bg-white px-6 py-12 text-center">
                            <div className="bg-secondary/10 flex size-12 items-center justify-center rounded-full">
                                <ReceiptText className="text-secondary size-6" aria-hidden />
                            </div>
                            <div className="text-base font-medium">{tr('liste.vide', 'Aucune facture sur cette période dans cette vue')}</div>
                            <p className="text-muted-foreground max-w-md text-sm">
                                {filtresActifs
                                    ? tr('liste.vide_filtres', 'Élargissez les filtres, ou choisissez une autre période.')
                                    : tr('liste.vide_aide', 'Créez une facture vierge, une proforma, ou générez la commission du mois.')}
                            </p>
                            <div className="flex flex-wrap justify-center gap-2">
                                {filtresActifs && (
                                    <Button type="button" variant="outline" onClick={reinitialiserFiltres}>
                                        {tr('liste.vide_effacer', 'Effacer les filtres')}
                                    </Button>
                                )}
                                {can.create && (
                                    <Button type="button" className="bg-secondary hover:bg-secondary/90" onClick={nouvelleFacture}>
                                        <Plus className="size-4" />
                                        {tr('liste.nouvelle', 'Nouvelle facture')}
                                    </Button>
                                )}
                            </div>
                        </div>
                    ) : (
                        <>
                            {/* Desktop : tableau */}
                            <div className="hidden overflow-hidden rounded-xl border bg-white shadow-sm md:block">
                                <Table>
                                    <TableHeader>
                                        <TableRow className="bg-muted/40 hover:bg-muted/40">
                                            {can.delete && (
                                                <TableHead className="w-10 pl-4" data-tour="factures-selection">
                                                    <Checkbox
                                                        checked={toutCoche ? true : choisis.length === 0 ? false : 'indeterminate'}
                                                        onCheckedChange={toutBasculer}
                                                        disabled={brouillonsVisibles.length === 0}
                                                        aria-label={tr('liste.selection_tout', 'Sélectionner tous les brouillons affichés')}
                                                    />
                                                </TableHead>
                                            )}
                                            <TableHead className={can.delete ? 'pl-1' : 'pl-4'}>{tr('liste.col_numero', 'Numéro')}</TableHead>
                                            <TableHead>{tr('liste.col_type', 'Type')}</TableHead>
                                            <TableHead>{tr('liste.col_date', 'Date')}</TableHead>
                                            <TableHead>{tr('liste.col_client', 'Client')}</TableHead>
                                            <TableHead>{tr('liste.col_echeance', 'Échéance')}</TableHead>
                                            <TableHead className="text-right">{tr('liste.col_net', 'Net à payer')}</TableHead>
                                            <TableHead>{tr('liste.col_statut', 'Statut')}</TableHead>
                                            <TableHead className="pr-4 text-right">
                                                <span className="sr-only">{tr('liste.col_document', 'Document')}</span>
                                            </TableHead>
                                        </TableRow>
                                    </TableHeader>
                                    <TableBody>
                                        {tranche.map((f) => (
                                            <TableRow
                                                key={f.id}
                                                onClick={(e) => ouvrirFacture(e, f)}
                                                className="cursor-pointer"
                                                data-state={selection.has(f.id) ? 'selected' : undefined}
                                            >
                                                {can.delete && (
                                                    <TableCell className="w-10 pl-4" onClick={(e) => e.stopPropagation()}>
                                                        {f.statut === 'brouillon' && (
                                                            <Checkbox
                                                                checked={selection.has(f.id)}
                                                                onCheckedChange={() => basculer(f.id)}
                                                                aria-label={tr('liste.selection_ligne', 'Sélectionner ce brouillon :client', {
                                                                    client: f.client_nom,
                                                                })}
                                                            />
                                                        )}
                                                    </TableCell>
                                                )}
                                                <TableCell className={can.delete ? 'pl-1' : 'pl-4'}>
                                                    <Link
                                                        href={route('factures.edit', f.id)}
                                                        onClick={(e) => e.stopPropagation()}
                                                        className={cn(
                                                            'hover:underline',
                                                            f.numero ? 'text-primary font-mono font-medium' : 'text-muted-foreground italic',
                                                        )}
                                                    >
                                                        {f.numero ?? tr('liste.brouillon', 'Brouillon')}
                                                    </Link>
                                                </TableCell>
                                                <TableCell>
                                                    <Badge variant="outline" className={cn('font-normal', f.type === 'proforma' && 'border-dashed')}>
                                                        {TYPE_LIBELLE[f.type]}
                                                    </Badge>
                                                </TableCell>
                                                <TableCell className="tabular-nums">{fmtDate(f.date_facture)}</TableCell>
                                                <TableCell className="max-w-72 whitespace-normal">
                                                    <div className="truncate font-medium" title={f.client_nom}>
                                                        {f.client_nom}
                                                    </div>
                                                    {f.origine_libelle && (
                                                        <div className="text-muted-foreground truncate text-xs" title={f.origine_libelle}>
                                                            {f.origine_libelle}
                                                        </div>
                                                    )}
                                                </TableCell>
                                                <TableCell className="tabular-nums">
                                                    <Echeance facture={f} tr={tr} />
                                                </TableCell>
                                                <TableCell className="text-right font-medium tabular-nums">
                                                    {fmtMontant(f.total_ttc, f.devise)}
                                                </TableCell>
                                                <TableCell>
                                                    <StatusChip statut={f.statut} enRetard={f.en_retard} type={f.type} />
                                                </TableCell>
                                                <TableCell className="pr-4">
                                                    <div className="flex items-center justify-end gap-2">
                                                        {f.envoyee_le && (
                                                            <span className="text-muted-foreground text-xs tabular-nums">
                                                                {tr('liste.envoyee_le', 'Envoyée le :date', {
                                                                    date: fmtDate(f.envoyee_le).slice(0, 5),
                                                                })}
                                                            </span>
                                                        )}
                                                        {f.statut !== 'brouillon' && (
                                                            <a
                                                                href={route('factures.pdf', f.id)}
                                                                target="_blank"
                                                                rel="noopener noreferrer"
                                                                onClick={(e) => e.stopPropagation()}
                                                                className="text-muted-foreground hover:bg-muted hover:text-primary inline-flex size-8 items-center justify-center rounded-md transition"
                                                                title={tr('liste.ouvrir_pdf', 'Ouvrir le PDF')}
                                                                aria-label={`${tr('liste.ouvrir_pdf', 'Ouvrir le PDF')} ${f.numero ?? ''}`.trim()}
                                                            >
                                                                <FileDown className="size-4" />
                                                            </a>
                                                        )}
                                                    </div>
                                                </TableCell>
                                            </TableRow>
                                        ))}
                                    </TableBody>
                                </Table>
                            </div>

                            {/* Mobile : cartes */}
                            <div className="grid gap-3 md:hidden">
                                {tranche.map((f) => (
                                    <div key={f.id} className="flex items-stretch gap-2">
                                        {can.delete && (
                                            <div className="flex w-6 shrink-0 items-center justify-center">
                                                {f.statut === 'brouillon' && (
                                                    <Checkbox
                                                        checked={selection.has(f.id)}
                                                        onCheckedChange={() => basculer(f.id)}
                                                        aria-label={tr('liste.selection_ligne', 'Sélectionner ce brouillon :client', {
                                                            client: f.client_nom,
                                                        })}
                                                    />
                                                )}
                                            </div>
                                        )}
                                        <Link
                                            href={route('factures.edit', f.id)}
                                            className="active:bg-muted/40 block min-w-0 flex-1 rounded-xl border bg-white p-3 shadow-sm transition"
                                        >
                                            <div className="flex items-center justify-between gap-2">
                                                <span
                                                    className={cn(
                                                        'text-sm',
                                                        f.numero ? 'text-primary font-mono font-medium' : 'text-muted-foreground italic',
                                                    )}
                                                >
                                                    {f.numero ?? tr('liste.brouillon', 'Brouillon')}
                                                </span>
                                                <StatusChip statut={f.statut} enRetard={f.en_retard} type={f.type} />
                                            </div>
                                            <div className="mt-1.5 flex items-end justify-between gap-3">
                                                <div className="min-w-0">
                                                    <div className="truncate text-sm font-medium">{f.client_nom}</div>
                                                    <div className="text-muted-foreground text-xs tabular-nums">
                                                        {TYPE_LIBELLE[f.type]} · {fmtDate(f.date_facture)}
                                                    </div>
                                                </div>
                                                <div className="shrink-0 text-right text-sm font-semibold tabular-nums">
                                                    {fmtMontant(f.total_ttc, f.devise)}
                                                </div>
                                            </div>
                                        </Link>
                                    </div>
                                ))}
                            </div>
                        </>
                    )}
                </div>

                {/* Pied : total et pagination */}
                {visibles.length > 0 && (
                    <div className="text-muted-foreground flex flex-wrap items-center justify-between gap-2 text-sm">
                        <span className="tabular-nums">
                            {visibles.length > 1
                                ? tr('liste.nb_factures', ':n factures', { n: visibles.length })
                                : tr('liste.nb_facture', ':n facture', { n: visibles.length })}
                        </span>
                        {nbPages > 1 && (
                            <div className="flex items-center gap-2">
                                <Button
                                    type="button"
                                    variant="outline"
                                    size="sm"
                                    disabled={pageAffichee === 1}
                                    onClick={() => setPageCourante(pageAffichee - 1)}
                                >
                                    <ChevronLeft className="size-4" />
                                    {tr('liste.precedent', 'Précédent')}
                                </Button>
                                <span className="tabular-nums">
                                    {pageAffichee} / {nbPages}
                                </span>
                                <Button
                                    type="button"
                                    variant="outline"
                                    size="sm"
                                    disabled={pageAffichee === nbPages}
                                    onClick={() => setPageCourante(pageAffichee + 1)}
                                >
                                    {tr('liste.suivant', 'Suivant')}
                                    <ChevronRight className="size-4" />
                                </Button>
                            </div>
                        )}
                    </div>
                )}

                {/* Légende des couleurs */}
                <div className="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-2 text-xs">
                    <span className="font-medium">{tr('liste.legende', 'Légende :')}</span>
                    {LEGENDE.map((l) => (
                        <span key={`${l.statut}-${l.enRetard}`} className="inline-flex items-center gap-1.5">
                            <StatusChip statut={l.statut} enRetard={l.enRetard} />
                            {l.texte}
                        </span>
                    ))}
                </div>
            </div>

            {can.create && <CommissionSheet open={commissionOuverte} onOpenChange={setCommissionOuverte} options={config} />}
            {can.create && <RapportAirbnbSheet open={rapportOuvert} onOpenChange={setRapportOuvert} tvaDefaut={config.tva_defaut} />}
            {can.create && <ReleveBancaireSheet open={releveOuvert} onOpenChange={setReleveOuvert} tvaDefaut={config.tva_defaut} />}
            {can.create && config.import_actif && <ImportDialog open={importOuvert} onOpenChange={setImportOuvert} />}
            {can.emettre && (
                // Les `prochains` sont ceux de l'année en cours (FacturePageService::index).
                <NumberingDialog
                    open={numerotationOuverte}
                    onOpenChange={setNumerotationOuverte}
                    prochains={prochains}
                    planchers={planchers}
                    annee={new Date().getFullYear()}
                />
            )}
        </AppLayout>
    );
}

type Traduire = (cle: string, fr: string, vars?: Record<string, string | number>) => string;

/** Échéance : en rose avec l'ancienneté du retard ; date de paiement sous une facture payée. */
function Echeance({ facture: f, tr }: { facture: FactureListe; tr: Traduire }) {
    if (!f.date_echeance) return <span className="text-muted-foreground">—</span>;

    if (f.en_retard) {
        return (
            <div className="text-rose-700">
                <div className="font-medium">{fmtDate(f.date_echeance)}</div>
                <div className="text-xs">{tr('liste.retard_jours', 'il y a :n j', { n: joursDepuis(f.date_echeance) })}</div>
            </div>
        );
    }

    return (
        <div>
            <div className={cn(f.statut !== 'emise' && 'text-muted-foreground')}>{fmtDate(f.date_echeance)}</div>
            {f.statut === 'payee' && f.payee_le && (
                <div className="text-xs text-emerald-700">{tr('liste.payee_le', 'payée le :date', { date: fmtDate(f.payee_le) })}</div>
            )}
        </div>
    );
}

function ItemCreation({
    icone: Icone,
    titre,
    description,
    onSelect,
    disabled = false,
}: {
    icone: LucideIcon;
    titre: string;
    description: string;
    onSelect: () => void;
    disabled?: boolean;
}) {
    return (
        <DropdownMenuItem onSelect={onSelect} disabled={disabled} className="items-start gap-3 py-2">
            <Icone className="text-primary mt-0.5 size-4" aria-hidden />
            <span className="grid min-w-0 gap-0.5">
                <span className="text-sm font-medium">{titre}</span>
                <span className="text-muted-foreground text-xs leading-snug">{description}</span>
            </span>
        </DropdownMenuItem>
    );
}

const KPI_ACCENTS = {
    amber: { carte: 'from-amber-50 via-white to-amber-100', titre: 'text-amber-700', valeur: 'text-amber-900' },
    rose: { carte: 'from-rose-50 via-white to-rose-100', titre: 'text-rose-700', valeur: 'text-rose-900' },
    slate: { carte: 'from-slate-50 via-white to-slate-100', titre: 'text-slate-600', valeur: 'text-slate-900' },
    sky: { carte: 'from-sky-50 via-white to-sky-100', titre: 'text-sky-700', valeur: 'text-sky-900' },
} as const;

function KpiCard({
    titre,
    valeur,
    detail,
    sousTitre,
    accent,
    montant = false,
    actif = false,
    onClick,
}: {
    titre: string;
    valeur: ReactNode;
    detail?: string;
    sousTitre: ReactNode;
    accent: keyof typeof KPI_ACCENTS;
    /** Valeur monétaire : corps plus petit, les montants sont longs. */
    montant?: boolean;
    actif?: boolean;
    onClick?: () => void;
}) {
    const c = KPI_ACCENTS[accent];
    const contenu = (
        <>
            <div className={cn('text-xs font-medium', c.titre)}>{titre}</div>
            <div className={cn('mt-1 font-bold tabular-nums sm:mt-2', montant ? 'text-lg sm:text-2xl' : 'text-2xl sm:text-3xl', c.valeur)}>
                {valeur}
            </div>
            {detail && <div className="text-muted-foreground mt-0.5 text-[11px] font-medium tabular-nums sm:text-xs">{detail}</div>}
            <div className="text-muted-foreground mt-1 text-[11px] sm:text-xs">{sousTitre}</div>
        </>
    );
    const classes = cn('min-w-0 rounded-xl border bg-gradient-to-br p-3 text-left shadow-sm sm:p-4', c.carte);

    if (!onClick) return <div className={classes}>{contenu}</div>;

    return (
        <button
            type="button"
            onClick={onClick}
            aria-pressed={actif}
            className={cn(
                classes,
                'focus-visible:ring-secondary/50 transition hover:shadow-md focus-visible:ring-2 focus-visible:outline-none',
                actif && 'ring-secondary/40 ring-2',
            )}
        >
            {contenu}
        </button>
    );
}
