import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
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 { cn } from '@/lib/utils';
import { AlertTriangle, Check, RotateCcw } from 'lucide-react';
import { useEffect, useId } from 'react';
import {
    FEE_MARKUP_PART_CODES,
    feeMarkupAnchor,
    groupModes,
    parseFeeMarkup,
    partsForMode,
    type RepushChannel,
    type RepushFormState,
    type RepushOptions,
} from '../repush-utils';

type Props = {
    channels: RepushChannel[];
    options: RepushOptions | null;
    loading: boolean;
    error: string | null;
    onRetry: () => void;
    value: RepushFormState;
    onChange: (next: RepushFormState) => void;
    showFeeMarkup: boolean;
    disabled?: boolean;
};

/** Groupe de modes retenu par le bouton radio. */
type ModeGroup = 'full' | 'partial';

/**
 * Sélecteur de contenu à republier, partagé par la modale unitaire et la page
 * en masse. Purement présentationnel : aucun appel réseau, aucun état interne
 * métier — le parent possède `value` et l'avancement du chargement.
 */
export default function RepushContentPicker({ channels, options, loading, error, onRetry, value, onChange, showFeeMarkup, disabled }: Props) {
    const { t } = useTranslation();
    // Préfixe d'instance : la modale et la page en masse montent le même
    // composant, les `htmlFor` doivent rester uniques dans le document.
    const uid = useId();
    const feeFieldId = `${uid}-fee`;
    const feeHelpId = `${uid}-fee-help`;

    const singleChannelId = channels.length === 1 ? channels[0].id : null;

    // Un seul canal connecté : le choix n'en est pas un, on le pose d'office.
    useEffect(() => {
        if (singleChannelId !== null && value.channelId === '') {
            onChange({ channelId: singleChannelId, modeName: '', checked: [], feeMarkupRaw: '', resetFeeMarkup: false });
        }
    }, [singleChannelId, value.channelId, onChange]);

    if (channels.length === 0) {
        return <p className="text-muted-foreground text-sm">{t('properties.repush', 'no_channel')}</p>;
    }

    // Changer de canal repart de zéro : les modes, les zones et la majoration
    // sont propres au canal, en garder un reliquat produirait un envoi absurde.
    const handleChannelChange = (channelId: string) => onChange({ channelId, modeName: '', checked: [], feeMarkupRaw: '', resetFeeMarkup: false });

    const groups = options ? groupModes(options.modes) : { full: [], partial: [] };
    const selectedMode = options?.modes.find((mode) => mode.name === value.modeName) ?? null;
    const currentGroup: ModeGroup | '' = selectedMode ? (selectedMode.is_partial ? 'partial' : 'full') : '';
    const isPartial = selectedMode?.is_partial === true;

    const handleGroupChange = (group: string) => {
        const first = group === 'partial' ? groups.partial[0] : groups.full[0];
        if (!first) return;
        // Les cases cochées sont conservées : `buildRepushPayload` n'envoie rien
        // en mode complet, et un aller-retour ne doit pas perdre la sélection.
        onChange({ ...value, modeName: first.name });
    };

    // En mode complet on affiche quand même les zones du premier mode partiel,
    // grisées : l'utilisateur voit ce qu'il obtiendrait en basculant.
    const previewModeName = isPartial ? value.modeName : (groups.partial[0]?.name ?? '');
    const displayedParts = options ? partsForMode(options.modes, previewModeName) : [];
    const partsDisabled = !isPartial || disabled === true;

    const togglePart = (code: string, next: boolean) =>
        onChange({ ...value, checked: next ? [...value.checked, code] : value.checked.filter((entry) => entry !== code) });

    const setAllParts = (next: boolean) =>
        onChange({
            ...value,
            checked: next
                ? Array.from(new Set([...value.checked, ...displayedParts.map((part) => part.code)]))
                : value.checked.filter((code) => !displayedParts.some((part) => part.code === code)),
        });

    const anchor = feeMarkupAnchor(displayedParts, FEE_MARKUP_PART_CODES);
    const anchorIndex = anchor === null ? -1 : displayedParts.findIndex((part) => part.code === anchor);
    // Le champ de majoration est imbriqué VISUELLEMENT sous sa zone d'ancrage,
    // mais ne peut pas être un descendant du <fieldset disabled> : c'est une
    // méthode RU indépendante, qui reste utilisable en mode complet. D'où la
    // liste coupée en deux fieldsets encadrant le champ.
    const partsBeforeFee = anchorIndex >= 0 ? displayedParts.slice(0, anchorIndex + 1) : displayedParts;
    const partsAfterFee = anchorIndex >= 0 ? displayedParts.slice(anchorIndex + 1) : [];

    const feeError = parseFeeMarkup(value.feeMarkupRaw).error;
    const feeIsReset = value.resetFeeMarkup === true;

    const feeHelpText = (): string => {
        if (!options || !options.fee_markup_available) return t('properties.repush', 'fee_markup_help_unknown');
        if (options.fee_markup_is_default === true) {
            return t('properties.repush', 'fee_markup_help_default').replace(':value', String(options.default_fee_markup ?? 0));
        }
        return t('properties.repush', 'fee_markup_help_known').replace(':value', String(options.fee_markup ?? 0));
    };

    const renderPartRow = (part: { code: string; label: string; known: boolean }) => (
        <div key={part.code} className="flex items-center gap-2">
            <Checkbox
                id={`${uid}-part-${part.code}`}
                checked={value.checked.includes(part.code)}
                onCheckedChange={(next) => togglePart(part.code, next === true)}
            />
            <Label htmlFor={`${uid}-part-${part.code}`} className="cursor-pointer text-sm">
                {part.label}
            </Label>
            {!part.known && (
                <Tooltip>
                    <TooltipTrigger asChild>
                        <Badge variant="outline" className="cursor-help border-amber-300 text-amber-700 dark:text-amber-300">
                            {t('properties.repush', 'part_unknown_badge')}
                        </Badge>
                    </TooltipTrigger>
                    <TooltipContent className="max-w-64">{t('properties.repush', 'part_unknown_tooltip')}</TooltipContent>
                </Tooltip>
            )}
        </div>
    );

    const feeMarkupField = (
        <div className="flex flex-col gap-1.5">
            <Label htmlFor={feeFieldId} className="text-sm font-medium">
                {t('properties.repush', 'fee_markup_label')}
            </Label>
            <div className="flex items-center gap-2">
                <Input
                    id={feeFieldId}
                    /* Un input type="number" vide silencieusement « 10,5 » sur un
                       navigateur français : on reste en texte et on parse nous-mêmes. */
                    type="text"
                    inputMode="decimal"
                    className="h-9 w-28"
                    value={value.feeMarkupRaw}
                    disabled={disabled === true || feeIsReset}
                    aria-describedby={feeHelpId}
                    aria-invalid={feeError !== null}
                    onChange={(event) => onChange({ ...value, feeMarkupRaw: event.target.value, resetFeeMarkup: false })}
                />
                <span className="text-muted-foreground text-sm">%</span>
            </div>
            <p id={feeHelpId} className={cn('text-xs', feeError !== null ? 'text-destructive' : 'text-muted-foreground')}>
                {feeError !== null ? t('properties.repush', 'fee_markup_invalid') : feeHelpText()}
            </p>
            {/* Bascule plutôt que lien : le retour à la valeur par défaut du canal
                est un choix qu'on doit pouvoir défaire avant d'envoyer. L'état
                est porté par aria-pressed, pas par un second libellé. */}
            {options?.fee_markup_is_default === false && (
                <button
                    type="button"
                    aria-pressed={feeIsReset}
                    disabled={disabled === true}
                    onClick={() => onChange({ ...value, feeMarkupRaw: '', resetFeeMarkup: !feeIsReset })}
                    className={cn(
                        'flex w-fit items-center gap-1 text-xs underline underline-offset-2 disabled:opacity-50',
                        feeIsReset ? 'text-primary font-medium' : 'text-muted-foreground hover:text-foreground',
                    )}
                >
                    {feeIsReset ? <Check className="h-3 w-3" /> : <RotateCcw className="h-3 w-3" />}
                    {t('properties.repush', 'fee_markup_reset_default')}
                </button>
            )}
        </div>
    );

    return (
        <div className="flex flex-col gap-4">
            <div className="flex flex-col gap-1.5">
                <Label htmlFor={`${uid}-channel`} className="text-sm font-medium">
                    {t('properties.repush', 'channel_label')}
                </Label>
                <Select value={value.channelId} onValueChange={handleChannelChange} disabled={disabled === true}>
                    <SelectTrigger id={`${uid}-channel`} className="w-full">
                        <SelectValue placeholder={t('properties.repush', 'channel_placeholder')} />
                    </SelectTrigger>
                    <SelectContent>
                        {channels.map((channel) => (
                            <SelectItem key={channel.id} value={channel.id}>
                                {channel.name}
                            </SelectItem>
                        ))}
                    </SelectContent>
                </Select>
            </div>

            <Alert className="border-amber-300 bg-amber-50 text-amber-800 dark:bg-amber-950/30 dark:text-amber-300">
                <AlertTriangle />
                <AlertDescription className="text-amber-800 dark:text-amber-300">{t('properties.repush', 'overwrite_warning')}</AlertDescription>
            </Alert>

            {loading && (
                <div className="flex items-center justify-center py-8">
                    <Spinner label={t('properties.repush', 'loading')} />
                </div>
            )}

            {!loading && error !== null && (
                <Alert variant="destructive" className="border-destructive/40">
                    <AlertTriangle />
                    <AlertDescription className="flex flex-col items-start gap-2">
                        <span>{error}</span>
                        <Button type="button" variant="outline" size="sm" onClick={onRetry} disabled={disabled === true}>
                            {t('properties.repush', 'retry')}
                        </Button>
                    </AlertDescription>
                </Alert>
            )}

            {!loading && error === null && options !== null && (
                <>
                    <div className="flex flex-col gap-2">
                        {/* Pas d'intitulé au-dessus : les deux libellés sont des
                            phrases complètes qui se suffisent, un titre de plus
                            ne ferait que répéter le titre de la fenêtre. */}
                        <RadioGroup value={currentGroup} onValueChange={handleGroupChange} disabled={disabled === true}>
                            {groups.full.length > 0 && (
                                <div className="flex items-center gap-2">
                                    <RadioGroupItem value="full" id={`${uid}-group-full`} />
                                    <Label htmlFor={`${uid}-group-full`} className="cursor-pointer text-sm">
                                        {t('properties.repush', 'group_full')}
                                    </Label>
                                </div>
                            )}
                            {groups.partial.length > 0 && (
                                <div className="flex items-center gap-2">
                                    <RadioGroupItem value="partial" id={`${uid}-group-partial`} />
                                    <Label htmlFor={`${uid}-group-partial`} className="cursor-pointer text-sm">
                                        {t('properties.repush', 'group_partial')}
                                    </Label>
                                </div>
                            )}
                        </RadioGroup>

                        {/* La portée n'a de sens qu'avec un choix : Airbnb n'a qu'un
                            mode par groupe, seuls les canaux multi-unités (Booking,
                            Expedia) exposent hôtel / chambre / les deux. */}
                        {currentGroup !== '' && (currentGroup === 'partial' ? groups.partial : groups.full).length > 1 && (
                            <div className="flex flex-col gap-1.5 pl-6">
                                <Label htmlFor={`${uid}-scope`} className="text-xs">
                                    {t('properties.repush', 'scope_label')}
                                </Label>
                                <Select
                                    value={value.modeName}
                                    onValueChange={(name) => onChange({ ...value, modeName: name })}
                                    disabled={disabled === true}
                                >
                                    <SelectTrigger id={`${uid}-scope`} className="w-full">
                                        <SelectValue />
                                    </SelectTrigger>
                                    <SelectContent>
                                        {(currentGroup === 'partial' ? groups.partial : groups.full).map((mode) => (
                                            <SelectItem key={mode.name} value={mode.name}>
                                                {mode.label}
                                            </SelectItem>
                                        ))}
                                    </SelectContent>
                                </Select>
                            </div>
                        )}
                    </div>

                    {displayedParts.length > 0 && (
                        <div className="flex flex-col gap-2">
                            <div className="flex items-center justify-between">
                                <span className={cn('text-sm font-medium', partsDisabled && 'text-muted-foreground')}>
                                    {t('properties.repush', 'parts_legend')}
                                </span>
                                <span className="flex items-center gap-3">
                                    <button
                                        type="button"
                                        disabled={partsDisabled}
                                        onClick={() => setAllParts(true)}
                                        className="text-xs underline underline-offset-2 disabled:opacity-50"
                                    >
                                        {t('properties.repush', 'select_all_parts')}
                                    </button>
                                    <button
                                        type="button"
                                        disabled={partsDisabled}
                                        onClick={() => setAllParts(false)}
                                        className="text-xs underline underline-offset-2 disabled:opacity-50"
                                    >
                                        {t('properties.repush', 'clear_all_parts')}
                                    </button>
                                </span>
                            </div>

                            {/* fieldset[disabled] rend le bloc nativement inerte ET
                                l'annonce aux lecteurs d'écran, ce qu'un simple
                                pointer-events-none ne fait pas. */}
                            <fieldset disabled={partsDisabled} className="flex flex-col gap-2 disabled:opacity-50">
                                {partsBeforeFee.map(renderPartRow)}
                            </fieldset>

                            {showFeeMarkup && anchorIndex >= 0 && <div className="border-input ml-2 border-l pl-4">{feeMarkupField}</div>}

                            {partsAfterFee.length > 0 && (
                                <fieldset disabled={partsDisabled} className="flex flex-col gap-2 disabled:opacity-50">
                                    {partsAfterFee.map(renderPartRow)}
                                </fieldset>
                            )}
                        </div>
                    )}

                    {showFeeMarkup && anchorIndex < 0 && feeMarkupField}
                </>
            )}
        </div>
    );
}
