import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { useTranslation } from '@/hooks/use-translation';
import { apiFetch } from '@/lib/loading/api-fetch';
import { Logement } from '@/types';
import { router, usePage } from '@inertiajs/react';
import { Loader2 } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import {
    buildRepushPayload,
    canSubmit,
    defaultMode,
    parseFeeMarkup,
    repushChannelsOf,
    type RepushFormState,
    type RepushOptions,
} from '../repush-utils';
import RepushContentPicker from './repush-content-picker';

type Props = {
    logement: Logement;
    open: boolean;
    onOpenChange: (open: boolean) => void;
};

const emptyState: RepushFormState = { channelId: '', modeName: '', checked: [], feeMarkupRaw: '', resetFeeMarkup: false };

/**
 * Demande à Rentals United de RENVOYER VERS LE CANAL le contenu qu'il détient
 * déjà (`CM_Push_UpdatePropertyContent_RQ`). Rien n'est poussé d'ici vers RU.
 *
 * Monté paresseusement par la carte : la grille affiche 20+ logements, un
 * sous-arbre Dialog par carte serait payé à chaque rendu de l'index.
 */
export default function ChannelRepushDialog({ logement, open, onOpenChange }: Props) {
    const { t } = useTranslation();
    const directChannels = (usePage().props.directChannels as { id: string; name: string; role: string }[] | undefined) ?? [];

    const channels = repushChannelsOf(logement.channel_listings ?? [], directChannels);

    const [state, setState] = useState<RepushFormState>(emptyState);
    const [options, setOptions] = useState<RepushOptions | null>(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [submitting, setSubmitting] = useState(false);
    const [retryTick, setRetryTick] = useState(0);

    // Garde d'identifiant : un changement rapide de canal laisse des réponses en
    // vol, et la dernière arrivée n'est pas forcément la dernière demandée.
    const requestId = useRef(0);

    // Capturé hors de l'effet : `t` est recréé à chaque rendu, l'utiliser comme
    // dépendance relancerait la requête en boucle.
    const loadErrorMessage = t('properties.repush', 'options_error');
    const channelId = state.channelId;

    useEffect(() => {
        if (!open || channelId === '') return;

        const id = ++requestId.current;
        let cancelled = false;

        setLoading(true);
        setError(null);
        setOptions(null);

        const load = async () => {
            try {
                const response = await apiFetch(route('logements.repush.options', { uid: logement.uid, channel_id: channelId }), {
                    headers: { Accept: 'application/json' },
                });
                const body = (await response.json().catch(() => null)) as (RepushOptions & { message?: string }) | null;

                if (cancelled || id !== requestId.current) return;

                if (!response.ok || body === null || !Array.isArray(body.modes)) {
                    setError(body?.message ?? loadErrorMessage);
                    return;
                }

                setOptions(body);
                // Le canal vient de changer : on repart du mode par défaut et d'une
                // sélection vide plutôt que de traîner les zones d'un autre canal.
                setState((previous) => ({
                    ...previous,
                    modeName: defaultMode(body.modes) ?? '',
                    checked: [],
                    feeMarkupRaw: '',
                    resetFeeMarkup: false,
                }));
            } catch {
                if (cancelled || id !== requestId.current) return;
                setError(loadErrorMessage);
            } finally {
                if (!cancelled && id === requestId.current) setLoading(false);
            }
        };

        void load();

        return () => {
            cancelled = true;
        };
    }, [open, channelId, retryTick, logement.uid, loadErrorMessage]);

    const payload = buildRepushPayload(state, options);
    const submittable = canSubmit(state, options);
    const isPartial = options?.modes.find((mode) => mode.name === state.modeName)?.is_partial === true;
    const feeError = parseFeeMarkup(state.feeMarkupRaw).error;

    // Un bouton désactivé sans raison visible est une impasse : on dit toujours
    // ce qui manque, dès que les options sont là pour que la raison soit vraie.
    const blockingReason =
        submittable || loading || error !== null || options === null
            ? null
            : feeError !== null
              ? t('properties.repush', 'fee_markup_invalid')
              : t('properties.repush', 'nothing_selected');

    const handleSubmit = () => {
        if (payload === null || submitting) return;

        router.post(route('logements.repush', { uid: logement.uid }), payload as Parameters<typeof router.post>[1], {
            preserveScroll: true,
            onStart: () => setSubmitting(true),
            onSuccess: () => onOpenChange(false),
            onFinish: () => setSubmitting(false),
        });
    };

    return (
        <Dialog open={open} onOpenChange={onOpenChange}>
            {/* Le corps défile seul : la liste Airbnb (10 zones) ne doit jamais
                pousser le pied de modale hors de l'écran. */}
            <DialogContent className="sm:max-w-lg">
                <DialogHeader>
                    <DialogTitle>{t('properties.repush', 'title')}</DialogTitle>
                    <DialogDescription>{t('properties.repush', 'description').replace(':property', logement.name)}</DialogDescription>
                </DialogHeader>

                <div className="max-h-[60vh] overflow-y-auto pr-1">
                    <RepushContentPicker
                        channels={channels}
                        options={options}
                        loading={loading}
                        error={error}
                        onRetry={() => setRetryTick((tick) => tick + 1)}
                        value={state}
                        onChange={setState}
                        showFeeMarkup
                        disabled={submitting}
                    />
                </div>

                <DialogFooter className="flex-col items-stretch gap-2 sm:flex-col sm:items-stretch">
                    {blockingReason !== null && <p className="text-muted-foreground text-xs sm:text-right">{blockingReason}</p>}
                    <div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
                        <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={submitting}>
                            {t('properties.repush', 'cancel')}
                        </Button>
                        <Button type="button" onClick={handleSubmit} disabled={!submittable || submitting}>
                            {submitting && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
                            {submitting
                                ? t('properties.repush', 'submitting')
                                : isPartial
                                  ? t('properties.repush', 'submit_partial')
                                  : t('properties.repush', 'submit_full')}
                        </Button>
                    </div>
                </DialogFooter>
            </DialogContent>
        </Dialog>
    );
}
