import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Pending } from '@/components/ui/pending';
import { Spinner } from '@/components/ui/spinner';
import { useTranslation } from '@/hooks/use-translation';
import { cn } from '@/lib/utils';
import { router } from '@inertiajs/react';
import { AlertTriangle, ArrowLeft, CheckCircle2, FileSpreadsheet, Link2, Send, XCircle } from 'lucide-react';
import * as React from 'react';
import { toast } from 'sonner';
import { appelJson, raisonEchec, remplir, urlRoute } from './api';
import { EtapeApercu } from './etape-apercu';
import { EtapeFichier } from './etape-fichier';
import { lireOngletsMemorises, memoriserOnglets, selectionInitiale, stockageNavigateur } from './memoire-onglets';
import { basculerFiltre, clesAConfirmer, clesALier, clesSelectionnees, filtresParDefaut, type FiltreApercu } from './preview-filtres';
import type { Apercu, EtatImport, ResultatCouleurs, ResultatImport } from './types';

interface Props {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    /** `import_sheet.peut_envoyer` — offre « Envoyer à la DGI maintenant » au résultat. */
    peutEnvoyer: boolean;
    /** Ouvre l'envoi en lot (le dialogue d'import est déjà refermé). */
    onEnvoyerMaintenant: () => void;
}

type Etape = 'fichier' | 'apercu' | 'resultat';

/**
 * IMPORTER LE FICHIER DES PAIEMENTS — trois étapes, une seule qui écrit.
 *
 * ═════════════════════════════════════════════════════════════════════════════
 * FICHIER → APERÇU → RÉSULTAT
 * ═════════════════════════════════════════════════════════════════════════════
 *   Fichier ... quel fichier, quels onglets (un par mois), et l'état des
 *               couleurs déjà posées. Lecture seule.
 *   Aperçu .... ce que donneraient les lignes BLANCHES portant un CIN ou un M/F.
 *               Lecture seule, relançable (date d'un onglet, « Relire »).
 *   Résultat .. après « Créer N lignes », le SEUL geste qui écrit. Le serveur
 *               relit le fichier à ce moment-là : il ne crée que ce qui est
 *               encore à importer, jamais sur la foi de l'aperçu affiché.
 *
 * Après une confirmation qui a créé ou lié, la page est rechargée à la
 * fermeture : les nouvelles lignes et le compteur « Envoyer les lignes
 * importées » apparaissent.
 */
export function ImportSheetDialog({ open, onOpenChange, peutEnvoyer, onEnvoyerMaintenant }: Props) {
    const { t } = useTranslation();
    /*
     * `t` change d'identité à chaque rendu (`useTranslation`) : le chargement
     * déclenché à l'ouverture la lit ici, sans quoi l'effet d'ouverture se
     * relancerait à chaque rendu — remise à zéro et relecture en boucle.
     */
    const traduire = React.useRef(t);
    React.useEffect(() => {
        traduire.current = t;
    });

    const [etape, setEtape] = React.useState<Etape>('fichier');

    // ── Étape 1 ──────────────────────────────────────────────────────────────
    const [etat, setEtat] = React.useState<EtatImport | null>(null);
    const [chargementEtat, setChargementEtat] = React.useState(false);
    const [erreurEtat, setErreurEtat] = React.useState<string | null>(null);
    const [ongletsChoisis, setOngletsChoisis] = React.useState<Set<number>>(new Set());
    const [couleursEnCours, setCouleursEnCours] = React.useState(false);

    // ── Étape 2 ──────────────────────────────────────────────────────────────
    const [apercu, setApercu] = React.useState<Apercu | null>(null);
    const [chargementApercu, setChargementApercu] = React.useState(false);
    /** Dates forcées par onglet (`gid` → `Y-m-d`) — seulement celles changées ici. */
    const [dates, setDates] = React.useState<Record<string, string>>({});
    const [filtres, setFiltres] = React.useState<Set<FiltreApercu>>(filtresParDefaut);
    const [decochees, setDecochees] = React.useState<Set<string>>(new Set());

    // ── Étape 3 ──────────────────────────────────────────────────────────────
    const [confirmation, setConfirmation] = React.useState(false);
    const [resultat, setResultat] = React.useState<ResultatImport | null>(null);

    /** L'erreur du dernier geste (aperçu, confirmation) — affichée au-dessus des boutons. */
    const [erreurAction, setErreurAction] = React.useState<string | null>(null);

    /*
     * Une réponse qui arrive après la fermeture — ou après une demande plus
     * récente DU MÊME GENRE — ne doit rien écrire à l'écran. Chaque appel prend
     * un numéro ; seul le dernier a le droit de répondre. Deux compteurs : une
     * relecture de l'état ne doit pas faire taire un aperçu, ni l'inverse.
     */
    const jetonEtat = React.useRef(0);
    const jetonApercu = React.useRef(0);
    /** Le fichier affiché — une relecture du MÊME fichier garde les onglets cochés. */
    const fichierAffiche = React.useRef<string | null>(null);

    const chargerEtat = React.useCallback(async (spreadsheet?: string) => {
        const numero = ++jetonEtat.current;
        setChargementEtat(true);
        setErreurEtat(null);

        const reponse = await appelJson<EtatImport>(urlRoute('retenue.import_sheet.etat', spreadsheet ? { spreadsheet } : undefined));
        if (numero !== jetonEtat.current) return;

        setChargementEtat(false);

        if (!reponse.ok) {
            setErreurEtat(raisonEchec(reponse, traduire.current));
            return;
        }

        const suivant = reponse.donnees;
        const fichier = suivant.spreadsheet;
        const lisibles = new Set(suivant.onglets.filter((o) => o.reconnu).map((o) => o.gid));
        const memeFichier = fichier !== null && fichier.id === fichierAffiche.current;

        fichierAffiche.current = fichier?.id ?? null;
        setEtat(suivant);
        setOngletsChoisis((avant) => {
            if (fichier === null) return new Set();
            if (memeFichier) return new Set([...avant].filter((gid) => lisibles.has(gid)));

            return selectionInitiale(suivant.onglets, lireOngletsMemorises(stockageNavigateur(), fichier.id));
        });
    }, []);

    // Ouverture : on repart d'une page blanche. Fermeture : on invalide les
    // réponses en vol.
    React.useEffect(() => {
        if (!open) {
            jetonEtat.current++;
            jetonApercu.current++;
            return;
        }

        fichierAffiche.current = null;
        setEtape('fichier');
        setEtat(null);
        setErreurEtat(null);
        setOngletsChoisis(new Set());
        setApercu(null);
        setDates({});
        setFiltres(filtresParDefaut());
        setDecochees(new Set());
        setResultat(null);
        setErreurAction(null);
        setChargementApercu(false);
        setConfirmation(false);
        setCouleursEnCours(false);

        void chargerEtat();
    }, [open, chargerEtat]);

    const aEcrit = resultat !== null && resultat.creees + resultat.liees > 0;

    /** Fermer — et recharger la page si quelque chose a été créé ou lié. */
    const fermer = () => {
        // La confirmation est en vol : la fermer perdrait son résultat.
        if (confirmation) return;
        onOpenChange(false);
        if (aEcrit) router.reload();
    };

    const basculerOnglet = (gid: number) => {
        const suivant = new Set(ongletsChoisis);
        if (suivant.has(gid)) suivant.delete(gid);
        else suivant.add(gid);

        setOngletsChoisis(suivant);
        if (etat?.spreadsheet) memoriserOnglets(stockageNavigateur(), etat.spreadsheet.id, [...suivant]);
    };

    const mettreAJourCouleurs = async () => {
        setCouleursEnCours(true);
        const reponse = await appelJson<ResultatCouleurs>(urlRoute('retenue.import_sheet.couleurs'), { methode: 'POST', corps: {} });
        setCouleursEnCours(false);

        if (!reponse.ok) {
            toast.error(raisonEchec(reponse, t));
            return;
        }

        if (reponse.donnees.ok) toast.success(reponse.donnees.message);
        else toast.error(reponse.donnees.message);

        // Le suivi a changé : on le relit sur le même fichier.
        void chargerEtat(etat?.spreadsheet?.id);
    };

    /** Les onglets choisis, dans l'ordre du fichier. */
    const gidsChoisis = (etat?.onglets ?? []).filter((o) => o.reconnu && ongletsChoisis.has(o.gid)).map((o) => o.gid);

    const lireApercu = async (options: { dates: Record<string, string>; onglets: number[]; spreadsheet: string; nouveau: boolean }) => {
        const numero = ++jetonApercu.current;
        setChargementApercu(true);
        setErreurAction(null);

        const reponse = await appelJson<Apercu>(urlRoute('retenue.import_sheet.apercu'), {
            methode: 'POST',
            corps: {
                spreadsheet: options.spreadsheet,
                onglets: options.onglets,
                ...(Object.keys(options.dates).length > 0 ? { dates: options.dates } : {}),
            },
        });
        if (numero !== jetonApercu.current) return;

        setChargementApercu(false);

        if (!reponse.ok) {
            setErreurAction(raisonEchec(reponse, t));
            return;
        }

        if (options.nouveau) {
            setFiltres(filtresParDefaut());
            setDecochees(new Set());
        }
        // Les dates ne sont retenues qu'avec l'aperçu qui les montre : la
        // confirmation renvoie exactement celles de l'écran affiché.
        setDates(options.dates);
        setApercu(reponse.donnees);
        setEtape('apercu');
    };

    /** « Lire les lignes » — un nouvel aperçu, cases et dates remises à zéro. */
    const lireLesLignes = () => {
        if (!etat?.spreadsheet || gidsChoisis.length === 0) return;
        void lireApercu({ dates: {}, onglets: gidsChoisis, spreadsheet: etat.spreadsheet.id, nouveau: true });
    };

    /** « Relire » ou une date changée : même fichier, mêmes onglets, cases gardées. */
    const relire = (datesSuivantes: Record<string, string> = dates) => {
        if (!apercu) return;
        void lireApercu({ dates: datesSuivantes, onglets: apercu.onglets.map((o) => o.gid), spreadsheet: apercu.spreadsheet.id, nouveau: false });
    };

    const appliquerDate = (gid: number, date: string) => relire({ ...dates, [String(gid)]: date });

    const basculerLigne = (cle: string) => {
        setDecochees((avant) => {
            const suivant = new Set(avant);
            if (suivant.has(cle)) suivant.delete(cle);
            else suivant.add(cle);

            return suivant;
        });
    };

    const nbACreer = apercu ? clesSelectionnees(apercu.onglets, decochees).length : 0;
    const nbALier = apercu ? clesALier(apercu.onglets).length : 0;

    const confirmer = async () => {
        if (!apercu || nbACreer + nbALier === 0) return;

        setConfirmation(true);
        setErreurAction(null);

        const reponse = await appelJson<ResultatImport>(urlRoute('retenue.import_sheet.confirmer'), {
            methode: 'POST',
            corps: {
                spreadsheet: apercu.spreadsheet.id,
                onglets: apercu.onglets.map((o) => o.gid),
                ...(Object.keys(dates).length > 0 ? { dates } : {}),
                cles: clesAConfirmer(apercu.onglets, decochees),
            },
        });

        setConfirmation(false);

        if (!reponse.ok) {
            setErreurAction(raisonEchec(reponse, t));
            return;
        }

        setResultat(reponse.donnees);
        setEtape('resultat');
    };

    const envoyerMaintenant = () => {
        fermer();
        onEnvoyerMaintenant();
    };

    /** Le libellé d'une clé refusée : son n° de ligne et son nom, quand l'aperçu la connaît. */
    const libelleCle = (cle: string): string => {
        for (const onglet of apercu?.onglets ?? []) {
            const ligne = onglet.lignes.find((l) => l.cle === cle);
            if (ligne) {
                return remplir(t('retenue', 'import_refus_ligne', ':onglet, ligne :numero — :nom'), {
                    onglet: onglet.titre,
                    numero: ligne.numero,
                    nom: ligne.nom_fichier,
                });
            }
        }

        return cle;
    };

    const etapes: Array<{ id: Etape; libelle: string }> = [
        { id: 'fichier', libelle: t('retenue', 'import_etape_fichier', 'Fichier') },
        { id: 'apercu', libelle: t('retenue', 'import_etape_apercu', 'Aperçu') },
        { id: 'resultat', libelle: t('retenue', 'import_etape_resultat', 'Résultat') },
    ];

    return (
        <Dialog open={open} onOpenChange={(valeur) => (valeur ? onOpenChange(true) : fermer())}>
            <DialogContent
                className={cn(
                    'flex max-h-[90vh] flex-col gap-3 p-4 sm:max-w-5xl sm:p-6',
                    // L'aperçu et le résultat sont des tableaux : à partir de
                    // 1 024 px, toute la largeur moins une marge.
                    etape !== 'fichier' && 'lg:max-w-[calc(100%-4rem)] 2xl:max-w-7xl',
                )}
                onInteractOutside={(e) => e.preventDefault()}
            >
                <DialogHeader>
                    <DialogTitle className="flex items-center gap-2">
                        <FileSpreadsheet className="h-5 w-5" />
                        {t('retenue', 'import_titre', 'Importer le fichier des paiements')}
                    </DialogTitle>
                    <DialogDescription>
                        {t(
                            'retenue',
                            'import_description',
                            'Seules les lignes blanches portant un CIN ou un M/F sont lues — les jaunes sont déjà déclarées. Rien n’est créé avant votre confirmation.',
                        )}
                    </DialogDescription>
                    <ol className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs">
                        {etapes.map((e, i) => (
                            <li key={e.id} className="flex items-center gap-2">
                                {i > 0 && <span className="text-muted-foreground">›</span>}
                                <span className={cn(e.id === etape ? 'text-foreground font-semibold' : 'text-muted-foreground')}>
                                    {i + 1}. {e.libelle}
                                </span>
                            </li>
                        ))}
                    </ol>
                </DialogHeader>

                <div className="-mx-1 min-h-0 flex-1 overflow-y-auto px-1 pb-1">
                    {etape === 'fichier' && (
                        <Pending
                            pending={chargementEtat && etat !== null}
                            message={t('retenue', 'import_etat_chargement', 'Lecture du fichier des paiements…')}
                        >
                            <EtapeFichier
                                etat={etat}
                                erreur={erreurEtat}
                                chargement={chargementEtat}
                                ongletsChoisis={ongletsChoisis}
                                onBasculerOnglet={basculerOnglet}
                                onChangerFichier={(lien) => void chargerEtat(lien)}
                                onRecharger={() => void chargerEtat(etat?.spreadsheet?.id)}
                                onMettreAJourCouleurs={() => void mettreAJourCouleurs()}
                                couleursEnCours={couleursEnCours}
                            />
                        </Pending>
                    )}

                    {etape === 'apercu' && apercu && (
                        <EtapeApercu
                            apercu={apercu}
                            chargement={chargementApercu}
                            filtres={filtres}
                            onBasculerFiltre={(f) => setFiltres((avant) => basculerFiltre(avant, f))}
                            decochees={decochees}
                            onBasculerLigne={basculerLigne}
                            onAppliquerDate={appliquerDate}
                            onRelire={() => relire()}
                        />
                    )}

                    {etape === 'resultat' && resultat && <EtapeResultat resultat={resultat} libelleCle={libelleCle} />}
                </div>

                {erreurAction && (
                    <div className="flex gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900">
                        <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
                        <span>{erreurAction}</span>
                    </div>
                )}

                {etape === 'apercu' && nbALier > 0 && (
                    <p className="text-muted-foreground text-xs">
                        {remplir(
                            t(
                                'retenue',
                                'import_a_lier',
                                ':n ligne(s) déjà présente(s) dans le manager seront liées au fichier, pour que leur couleur y soit suivie. Rien n’est créé pour elles.',
                            ),
                            { n: nbALier },
                        )}
                    </p>
                )}

                <DialogFooter className="gap-2 sm:gap-2">
                    {etape === 'fichier' && (
                        <>
                            <Button variant="outline" onClick={fermer}>
                                {t('retenue', 'cancel', 'Annuler')}
                            </Button>
                            <Button
                                onClick={lireLesLignes}
                                disabled={!etat?.spreadsheet || gidsChoisis.length === 0 || chargementApercu || chargementEtat}
                            >
                                {chargementApercu && <Spinner size="sm" className="mr-2 text-current" />}
                                {t('retenue', 'import_lire', 'Lire les lignes')}
                            </Button>
                        </>
                    )}

                    {etape === 'apercu' && (
                        <>
                            <Button variant="ghost" className="sm:mr-auto" onClick={() => setEtape('fichier')} disabled={confirmation}>
                                <ArrowLeft className="mr-2 h-4 w-4" />
                                {t('retenue', 'import_retour_onglets', 'Changer d’onglets')}
                            </Button>
                            <Button variant="outline" onClick={fermer} disabled={confirmation}>
                                {t('retenue', 'cancel', 'Annuler')}
                            </Button>
                            <Button onClick={() => void confirmer()} disabled={nbACreer + nbALier === 0 || confirmation || chargementApercu}>
                                {confirmation ? <Spinner size="sm" className="mr-2 text-current" /> : <CheckCircle2 className="mr-2 h-4 w-4" />}
                                {nbACreer > 0
                                    ? remplir(t('retenue', 'import_creer', 'Créer :n lignes'), { n: nbACreer })
                                    : remplir(t('retenue', 'import_lier_seulement', 'Lier :n lignes au fichier'), { n: nbALier })}
                            </Button>
                        </>
                    )}

                    {etape === 'resultat' && (
                        <>
                            <Button variant="outline" onClick={fermer}>
                                {t('retenue', 'import_fermer', 'Fermer')}
                            </Button>
                            {peutEnvoyer && resultat !== null && resultat.creees > 0 && (
                                <Button onClick={envoyerMaintenant}>
                                    <Send className="mr-2 h-4 w-4" />
                                    {t('retenue', 'import_envoyer_maintenant', 'Envoyer à la DGI maintenant')}
                                </Button>
                            )}
                        </>
                    )}
                </DialogFooter>
            </DialogContent>
        </Dialog>
    );
}

/** Étape 3 — ce qui a été créé, lié, refusé, et si le fichier a pu être marqué. */
function EtapeResultat({ resultat, libelleCle }: { resultat: ResultatImport; libelleCle: (cle: string) => string }) {
    const { t } = useTranslation();

    return (
        <div className="flex flex-col gap-3">
            <div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
                <Tuile
                    icone={<CheckCircle2 className="h-4 w-4 text-emerald-600" />}
                    libelle={t('retenue', 'import_res_creees', 'Lignes créées')}
                    valeur={resultat.creees}
                />
                <Tuile
                    icone={<Link2 className="h-4 w-4 text-sky-600" />}
                    libelle={t('retenue', 'import_res_liees', 'Lignes liées au fichier')}
                    valeur={resultat.liees}
                />
                <Tuile
                    icone={<XCircle className="h-4 w-4 text-red-600" />}
                    libelle={t('retenue', 'import_res_refusees', 'Lignes refusées')}
                    valeur={resultat.refusees.length}
                />
            </div>

            {resultat.creees + resultat.liees === 0 && (
                <p className="text-muted-foreground text-sm">{t('retenue', 'import_res_rien', 'Aucune ligne n’a été créée ni liée.')}</p>
            )}

            {resultat.refusees.length > 0 && (
                <div className="flex flex-col gap-1.5">
                    <p className="text-sm font-medium">
                        {t('retenue', 'import_res_refusees_titre', 'Refusées à la relecture du fichier — rien n’a été créé pour elles :')}
                    </p>
                    <ul className="flex flex-col gap-1 rounded-md border text-sm">
                        {resultat.refusees.map((r) => (
                            <li key={r.cle} className="flex flex-col border-b px-3 py-1.5 last:border-b-0">
                                <span className="font-medium">{libelleCle(r.cle)}</span>
                                <span className="text-xs text-red-700">{r.motif}</span>
                            </li>
                        ))}
                    </ul>
                </div>
            )}

            {!resultat.fichier.ok && (
                <div className="flex gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900">
                    <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
                    <span>
                        {remplir(t('retenue', 'import_res_fichier_ko', 'Les lignes sont créées ; le fichier n’a pas pu être marqué : :message'), {
                            message: resultat.fichier.message ?? '—',
                        })}
                    </span>
                </div>
            )}
        </div>
    );
}

function Tuile({ icone, libelle, valeur }: { icone: React.ReactNode; libelle: string; valeur: number }) {
    return (
        <div className="flex items-center gap-3 rounded-lg border px-3 py-2">
            {icone}
            <div className="flex flex-col">
                <span className="text-muted-foreground text-[11px] tracking-wide uppercase">{libelle}</span>
                <span className="text-lg font-semibold tabular-nums">{valeur}</span>
            </div>
        </div>
    );
}
