import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/components/ui/command';
import { Input } from '@/components/ui/input';
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import type { ActivityKpis, ActivityOption, ActivityPageProps, ActivityParent } from '@/types/activity';
import { Bot, Building2, CalendarDays, CalendarRange, Check, ChevronLeft, History, Search, ShieldAlert, User, X, Zap, type LucideIcon } from 'lucide-react';
import * as React from 'react';
import type { DateRange } from 'react-day-picker';
import { useMediaQuery } from 'usehooks-ts';
import {
    activeFilterCount,
    DEFAULT_PRESET,
    dateToIsoDay,
    detectPreset,
    effectiveRange,
    fill,
    formatCount,
    formatShortDay,
    isActivityEvent,
    isoDayToDate,
    PERIOD_PRESETS,
    presetRange,
    type ActivityQuery,
    type PeriodPreset,
    type Translate,
} from './activity-format';

const NS = 'activity';

/**
 * Langage commun de la barre : une hauteur (h-9), un rayon (rounded-md), un
 * contour. Un contrôle qui s'en écarte se remarque avant d'être lu.
 */
const CONTROL = 'h-9 rounded-md border border-input bg-background text-sm shadow-xs';

interface Props {
    query: ActivityQuery;
    /** Texte tapé dans la recherche — tenu par la page, qui le vide à la réinitialisation. */
    searchInput: string;
    onSearchInput: (value: string) => void;
    /** Applique un changement de filtres (la recherche déjà tapée part avec). */
    onApply: (patch: Partial<ActivityQuery>) => void;
    /** « Tout effacer » : tous les filtres à leur défaut, l'onglet reste. */
    onReset: () => void;
    options: ActivityPageProps['options'];
    kpis: ActivityKpis;
    isHeadquarters: boolean;
    /** Libellé de l'élément filtré (`filters.subject`), fourni par le serveur. */
    subjectInfo: ActivityParent | null;
    now: Date;
    locale: string;
    t: Translate;
}

/* ─────────────────────────────────────────────────────────────────────────
 * Période
 * ───────────────────────────────────────────────────────────────────────── */

function PeriodButton({ query, onApply, now, t }: Pick<Props, 'query' | 'onApply' | 'now' | 't'>) {
    const [open, setOpen] = React.useState(false);
    const [mode, setMode] = React.useState<'presets' | 'custom'>('presets');
    const [draft, setDraft] = React.useState<DateRange | undefined>(undefined);
    const wide = useMediaQuery('(min-width: 640px)');

    // Toujours la période RÉELLEMENT appliquée — la fenêtre par défaut du
    // serveur quand l'URL n'en porte pas. Jamais « Choisir une période ».
    const range = effectiveRange(query, now);
    const active = detectPreset(range.from, range.to, now);

    const labels: Record<PeriodPreset, string> = {
        today: t(NS, 'preset_today', "Aujourd'hui"),
        yesterday: t(NS, 'preset_yesterday', 'Hier'),
        last7: t(NS, 'preset_last7', '7 derniers jours'),
        last30: t(NS, 'preset_last30', '30 derniers jours'),
        this_month: t(NS, 'preset_this_month', 'Ce mois-ci'),
    };

    const span = (from: string, to: string) =>
        from === to ? formatShortDay(from, now) : `${formatShortDay(from, now)} → ${formatShortDay(to, now)}`;

    const onOpenChange = (next: boolean) => {
        setOpen(next);
        if (next) {
            setMode('presets');
            setDraft({ from: isoDayToDate(range.from), to: isoDayToDate(range.to) });
        }
    };

    const choose = (preset: PeriodPreset) => {
        // La fenêtre par défaut ne s'écrit pas dans l'URL : le serveur l'applique seul.
        onApply(preset === DEFAULT_PRESET ? { from: null, to: null } : presetRange(preset, now));
        setOpen(false);
    };

    const applyCustom = () => {
        if (!draft?.from) return;
        const from = dateToIsoDay(draft.from);
        onApply({ from, to: draft.to ? dateToIsoDay(draft.to) : from });
        setOpen(false);
    };

    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const rangeEnd = isoDayToDate(range.to) ?? today;
    // Sur deux mois, la fin de la période s'affiche à DROITE : le mois d'avant
    // est à gauche, jamais un mois futur vide.
    const firstMonth = new Date(rangeEnd.getFullYear(), rangeEnd.getMonth() - (wide ? 1 : 0), 1);

    return (
        <Popover open={open} onOpenChange={onOpenChange}>
            <PopoverTrigger asChild>
                <button
                    type="button"
                    className={cn(
                        CONTROL,
                        'hover:bg-accent focus-visible:border-ring focus-visible:ring-ring/50 inline-flex shrink-0 items-center gap-2 px-3 whitespace-nowrap outline-none focus-visible:ring-[3px]',
                    )}
                >
                    <CalendarDays className="text-muted-foreground h-4 w-4" aria-hidden="true" />
                    <span className="sr-only">{t(NS, 'filter_period', 'Période')} </span>
                    {active ? (
                        <span>
                            <span className="font-medium">{labels[active]}</span>
                            <span className="text-muted-foreground"> · {span(range.from, range.to)}</span>
                        </span>
                    ) : (
                        <span className="font-medium">
                            {fill(t(NS, 'period_range', 'Du :from au :to'), { from: formatShortDay(range.from, now), to: formatShortDay(range.to, now) })}
                        </span>
                    )}
                </button>
            </PopoverTrigger>
            <PopoverContent align="start" className="w-auto p-0">
                {mode === 'presets' ? (
                    <Command className="w-64">
                        <CommandList>
                            <CommandGroup>
                                {PERIOD_PRESETS.map((preset) => {
                                    const presetDays = presetRange(preset, now);

                                    return (
                                        <CommandItem key={preset} value={preset} onSelect={() => choose(preset)}>
                                            <Check className={cn('text-primary h-4 w-4', active === preset ? 'opacity-100' : 'opacity-0')} aria-hidden="true" />
                                            <span>{labels[preset]}</span>
                                            <span className="text-muted-foreground ml-auto text-xs tabular-nums">{span(presetDays.from, presetDays.to)}</span>
                                        </CommandItem>
                                    );
                                })}
                            </CommandGroup>
                            <CommandSeparator />
                            <CommandGroup>
                                <CommandItem value="custom" onSelect={() => setMode('custom')}>
                                    <Check className={cn('text-primary h-4 w-4', active === null ? 'opacity-100' : 'opacity-0')} aria-hidden="true" />
                                    <span>{t(NS, 'period_custom', 'Dates précises…')}</span>
                                    <CalendarRange className="ml-auto h-4 w-4" aria-hidden="true" />
                                </CommandItem>
                            </CommandGroup>
                        </CommandList>
                    </Command>
                ) : (
                    <div className="flex flex-col">
                        <Calendar
                            mode="range"
                            numberOfMonths={wide ? 2 : 1}
                            defaultMonth={firstMonth}
                            selected={draft}
                            onSelect={(next) => setDraft(next)}
                            // Rien n'est encore arrivé demain : les jours futurs ne se choisissent pas.
                            disabled={{ after: today }}
                            initialFocus
                        />
                        <div className="flex items-center justify-between gap-2 border-t p-2">
                            <Button variant="ghost" size="sm" onClick={() => setMode('presets')}>
                                <ChevronLeft className="h-4 w-4" aria-hidden="true" />
                                {t(NS, 'period_back', 'Retour')}
                            </Button>
                            <Button size="sm" onClick={applyCustom} disabled={!draft?.from}>
                                {t(NS, 'period_apply', 'Appliquer')}
                            </Button>
                        </div>
                    </div>
                )}
            </PopoverContent>
        </Popover>
    );
}

/* ─────────────────────────────────────────────────────────────────────────
 * Pastilles de filtre
 * ───────────────────────────────────────────────────────────────────────── */

interface ChipProps {
    icon: LucideIcon;
    label: string;
    value: string | null;
    options: ActivityOption[];
    onSelect: (id: string) => void;
    onClear: () => void;
    /** Liste avec recherche — pour les membres, qui peuvent être plus de quarante. */
    searchPlaceholder?: string;
    notFound?: string;
    t: Translate;
}

/**
 * Vide : bordure en pointillés, texte atténué — une invitation. Posée :
 * teinte de la marque, « Membre : Farouk », et un × séparé pour la retirer.
 * Déclencheur et × sont deux boutons FRÈRES dans un span : jamais un bouton
 * dans un bouton.
 */
function FilterChip({ icon: Icon, label, value, options, onSelect, onClear, searchPlaceholder, notFound, t }: ChipProps) {
    const [open, setOpen] = React.useState(false);
    const active = value !== null;
    const valueName = active ? (options.find((option) => option.id === value)?.name ?? `#${value}`) : '';

    return (
        <Popover open={open} onOpenChange={setOpen}>
            <PopoverAnchor asChild>
                <span
                    className={cn(
                        'inline-flex h-9 max-w-[18rem] shrink-0 items-center rounded-md border text-sm transition-colors',
                        // Pointillés un ton au-dessus de `border-input` : à la même teinte que les
                        // contours pleins, un trait sur deux disparaissait.
                        active
                            ? 'border-primary/30 bg-primary/5 text-primary'
                            : 'border-muted-foreground/40 text-muted-foreground hover:bg-muted/50 hover:text-foreground border-dashed',
                    )}
                >
                    <PopoverTrigger asChild>
                        <button
                            type="button"
                            className={cn(
                                'focus-visible:ring-ring/50 inline-flex h-full min-w-0 items-center gap-1.5 rounded-md px-2.5 outline-none focus-visible:ring-[3px]',
                                active && 'pr-1',
                            )}
                        >
                            <Icon className="h-4 w-4 shrink-0" aria-hidden="true" />
                            <span className="truncate">
                                {active ? fill(t(NS, 'chip_value', ':label : :value'), { label, value: valueName }) : label}
                            </span>
                        </button>
                    </PopoverTrigger>
                    {active && (
                        <button
                            type="button"
                            onClick={onClear}
                            aria-label={fill(t(NS, 'clear_filter_named', 'Retirer le filtre :name'), { name: label })}
                            title={fill(t(NS, 'clear_filter_named', 'Retirer le filtre :name'), { name: label })}
                            className="hover:bg-primary/10 focus-visible:ring-ring/50 mr-1 flex h-6 w-6 shrink-0 items-center justify-center rounded outline-none focus-visible:ring-[3px]"
                        >
                            <X className="h-3.5 w-3.5" aria-hidden="true" />
                        </button>
                    )}
                </span>
            </PopoverAnchor>
            <PopoverContent align="start" className="w-64 p-0">
                <Command>
                    {searchPlaceholder && <CommandInput placeholder={searchPlaceholder} />}
                    <CommandList>
                        {notFound && <CommandEmpty>{notFound}</CommandEmpty>}
                        <CommandGroup>
                            {options.map((option) => (
                                <CommandItem
                                    key={option.id}
                                    // Valeur = l'identifiant (unique, même pour deux homonymes) ; le nom sert à la recherche.
                                    value={option.id}
                                    keywords={[option.name]}
                                    onSelect={() => {
                                        onSelect(option.id);
                                        setOpen(false);
                                    }}
                                >
                                    <Check className={cn('text-primary h-4 w-4', option.id === value ? 'opacity-100' : 'opacity-0')} aria-hidden="true" />
                                    <span className="truncate">{option.name}</span>
                                </CommandItem>
                            ))}
                        </CommandGroup>
                    </CommandList>
                </Command>
            </PopoverContent>
        </Popover>
    );
}

/** Teinte d'une pastille posée : la marque par défaut ; l'ambre pour « Sensibles » (même ton que ces gestes dans le flux). */
const ACTIVE_PRIMARY = 'border-primary/30 bg-primary/5 text-primary';
const ACTIVE_SENSITIVE = 'border-amber-300 bg-amber-100 text-amber-800';

/**
 * Pastille-bascule dans la MÊME grammaire que les filtres : pointillés quand
 * elle est éteinte (le défaut), teinte et × quand elle est allumée. Sert aux
 * actions automatiques (siège) — un interrupteur à libellé long finissait seul
 * sur une seconde ligne dès 1280 px — et aux gestes sensibles.
 */
function ToggleChip({
    icon: Icon,
    label,
    on,
    onToggle,
    title,
    activeClassName = ACTIVE_PRIMARY,
    t,
}: {
    icon: LucideIcon;
    label: string;
    on: boolean;
    onToggle: (next: boolean) => void;
    /** Ce que fait la bascule — l'infobulle dit ce qu'un seul mot ne peut pas dire. */
    title: string;
    activeClassName?: string;
    t: Translate;
}) {
    const clearLabel = fill(t(NS, 'clear_filter_named', 'Retirer le filtre :name'), { name: label });

    return (
        <span
            className={cn(
                'inline-flex h-9 shrink-0 items-center rounded-md border text-sm transition-colors',
                on ? activeClassName : 'border-muted-foreground/40 text-muted-foreground hover:bg-muted/50 hover:text-foreground border-dashed',
            )}
        >
            <button
                type="button"
                aria-pressed={on}
                title={title}
                onClick={() => onToggle(!on)}
                className={cn(
                    'focus-visible:ring-ring/50 inline-flex h-full items-center gap-1.5 rounded-md px-2.5 whitespace-nowrap outline-none focus-visible:ring-[3px]',
                    on && 'pr-1',
                )}
            >
                <Icon className="h-4 w-4 shrink-0" aria-hidden="true" />
                {label}
            </button>
            {on && (
                <button
                    type="button"
                    onClick={() => onToggle(false)}
                    aria-label={clearLabel}
                    title={clearLabel}
                    className="focus-visible:ring-ring/50 mr-1 flex h-6 w-6 shrink-0 items-center justify-center rounded outline-none hover:bg-black/5 focus-visible:ring-[3px]"
                >
                    <X className="h-3.5 w-3.5" aria-hidden="true" />
                </button>
            )}
        </span>
    );
}

/**
 * « Élément : Réservation #1788 · Ghazi » — posée par le bouton « Historique »
 * d'une fiche. Pas de liste à ouvrir : on ne choisit pas un élément ici, on
 * n'en retire que le filtre.
 */
function SubjectChip({ value, info, onClear, t }: { value: string; info: ActivityParent | null; onClear: () => void; t: Translate }) {
    const label = t(NS, 'filter_subject', 'Élément');
    // Tant que le serveur n'a pas renvoyé le libellé, on montre au moins le numéro.
    const id = value.split(':')[1];
    const name = info ? `${info.type_label} ${info.label}` : id ? `#${id}` : value;
    const text = fill(t(NS, 'chip_value', ':label : :value'), { label, value: name });
    const clearLabel = fill(t(NS, 'clear_filter_named', 'Retirer le filtre :name'), { name: label });

    return (
        <span className={cn('inline-flex h-9 max-w-[20rem] shrink-0 items-center rounded-md border text-sm', ACTIVE_PRIMARY)} title={text}>
            <span className="inline-flex h-full min-w-0 items-center gap-1.5 pr-1 pl-2.5">
                <History className="h-4 w-4 shrink-0" aria-hidden="true" />
                <span className="truncate">{text}</span>
            </span>
            <button
                type="button"
                onClick={onClear}
                aria-label={clearLabel}
                title={clearLabel}
                className="hover:bg-primary/10 focus-visible:ring-ring/50 mr-1 flex h-6 w-6 shrink-0 items-center justify-center rounded outline-none focus-visible:ring-[3px]"
            >
                <X className="h-3.5 w-3.5" aria-hidden="true" />
            </button>
        </span>
    );
}

/* ─────────────────────────────────────────────────────────────────────────
 * La barre
 * ───────────────────────────────────────────────────────────────────────── */

/**
 * Une seule rangée : recherche, période, pastilles, « Tout effacer », et au
 * siège l'interrupteur des actions automatiques. Tout vit dans l'URL : chaque
 * choix repart au serveur (`router.get` avec preserveState), la recherche
 * après 350 ms de silence.
 *
 * Onglet Équipe : la même barre réduite à la période et à la franchise — les
 * seuls critères dont dépendent ses chiffres (la page retire les autres en
 * changeant d'onglet).
 *
 * Deux blocs qui passent à la ligne EN BLOC, jamais pièce par pièce : la
 * recherche, puis le groupe période + pastilles + « Tout effacer ». Écran
 * large : une seule rangée. Écran plus étroit (siège à 1280 px avec des
 * filtres posés) : la recherche sur une ligne, tous les filtres sur la
 * suivante — deux rangées voulues, jamais un bouton orphelin. Au téléphone,
 * le groupe défile de côté au lieu de s'empiler.
 */
export function ActivityFilters({ query, searchInput, onSearchInput, onApply, onReset, options, kpis, isHeadquarters, subjectInfo, now, locale, t }: Props) {
    const isFeed = query.tab === 'feed';
    const count = activeFilterCount(query, now);
    /** La pastille des actions automatiques : siège seulement, onglet Flux seulement. */
    const showSystem = isFeed && isHeadquarters;

    // Recherche différée. Garde de premier rendu : sans elle, le montage
    // rejouerait une visite et écraserait les filtres restaurés par l'URL.
    const firstRender = React.useRef(true);
    React.useEffect(() => {
        if (firstRender.current) {
            firstRender.current = false;
            return;
        }
        const wanted = searchInput.trim() || null;
        if (wanted === query.search) return;

        const timer = setTimeout(() => onApply({ search: wanted }), 350);
        return () => clearTimeout(timer);
    }, [searchInput, query.search, onApply]);

    const memberLabel = t(NS, 'filter_member', 'Membre');
    const eventLabel = t(NS, 'filter_event', 'Action');
    const franchiseLabel = t(NS, 'filter_franchise', 'Franchise');

    return (
        <div data-tour="activity-filters" className="flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center">
            {isFeed && (
                // Largeur SOUPLE : la recherche rétrécit (jusqu'à 11 rem) avant que
                // la rangée ne passe à la ligne, et ne dépasse jamais 20 rem.
                <div className="relative w-full sm:w-auto sm:max-w-80 sm:min-w-44 sm:flex-1">
                    <Search className="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 h-4 w-4 -translate-y-1/2" aria-hidden="true" />
                    <Input
                        type="search"
                        value={searchInput}
                        onChange={(e) => onSearchInput(e.target.value)}
                        // Même borne que la règle serveur (`search` max:100) : au-delà,
                        // la requête serait refusée en silence par la validation.
                        maxLength={100}
                        placeholder={t(NS, 'search_placeholder', 'Voyageur, n° de réservation, client, logement…')}
                        aria-label={t(NS, 'filter_search', 'Recherche')}
                        className={cn(CONTROL, 'pl-8')}
                    />
                </div>
            )}

            {/* Téléphone : une ligne qui défile de côté. Au-delà : le groupe passe à la
                ligne en bloc, et ne se replie sur lui-même qu'une fois seul sur sa ligne
                (jamais de barre de défilement horizontale sur un écran d'ordinateur). */}
            <div className="-mx-3 -my-1 flex max-w-[calc(100%+1.5rem)] min-w-0 items-center gap-1.5 overflow-x-auto px-3 py-1 sm:mx-0 sm:max-w-full sm:flex-wrap sm:overflow-visible sm:px-0">
                <PeriodButton query={query} onApply={onApply} now={now} t={t} />

                {isFeed && query.subject !== null && <SubjectChip value={query.subject} info={subjectInfo} onClear={() => onApply({ subject: null })} t={t} />}

                {isFeed && (
                    <FilterChip
                        icon={User}
                        label={memberLabel}
                        value={query.user}
                        options={options.users}
                        onSelect={(id) => onApply({ user: id })}
                        onClear={() => onApply({ user: null })}
                        searchPlaceholder={t(NS, 'member_search', 'Rechercher un membre…')}
                        notFound={t(NS, 'member_not_found', 'Aucun membre trouvé')}
                        t={t}
                    />
                )}

                {isFeed && (
                    <FilterChip
                        icon={Zap}
                        label={eventLabel}
                        value={query.event}
                        options={options.events}
                        onSelect={(id) => onApply({ event: isActivityEvent(id) ? id : null })}
                        onClear={() => onApply({ event: null })}
                        t={t}
                    />
                )}

                {options.franchises !== null && (
                    <FilterChip
                        icon={Building2}
                        label={franchiseLabel}
                        value={query.franchise}
                        options={options.franchises}
                        onSelect={(id) => onApply({ franchise: id })}
                        onClear={() => onApply({ franchise: null })}
                        t={t}
                    />
                )}

                {isFeed && (
                    <ToggleChip
                        icon={ShieldAlert}
                        label={t(NS, 'filter_sensitive', 'Sensibles')}
                        title={t(NS, 'filter_sensitive_hint', 'Seulement les suppressions, les droits modifiés et les échecs de connexion')}
                        on={query.sensitive}
                        onToggle={(next) => onApply({ sensitive: next })}
                        activeClassName={ACTIVE_SENSITIVE}
                        t={t}
                    />
                )}

                {showSystem && (
                    <ToggleChip
                        icon={Bot}
                        label={t(NS, 'filter_system_chip', 'Automatiques')}
                        title={query.system ? t(NS, 'filter_system_chip_on', 'Automatiques incluses') : t(NS, 'filter_system', 'Inclure les actions automatiques')}
                        on={query.system}
                        onToggle={(next) => onApply({ system: next })}
                        t={t}
                    />
                )}

                {/* Toujours collé aux pastilles : il suit le groupe quand celui-ci passe à la ligne. */}
                {count > 0 && <ClearAll count={count} onReset={onReset} t={t} />}
            </div>

            {/* Onglet Équipe : le résumé, poussé à droite. */}
            {!isFeed && (
                <p className="text-muted-foreground flex h-9 items-center text-sm sm:ml-auto">
                    {fill(kpis.team_size === 1 ? t(NS, 'team_members_one', ':count membre') : t(NS, 'team_members_other', ':count membres'), {
                        count: formatCount(kpis.team_size, locale),
                    })}
                    {' · '}
                    {fill(kpis.active_members === 1 ? t(NS, 'team_active_one', ':count actif') : t(NS, 'team_active_other', ':count actifs'), {
                        count: formatCount(kpis.active_members, locale),
                    })}
                </p>
            )}
        </div>
    );
}

/** « Tout effacer (n) » — n'existe que lorsqu'un critère restreint la vue. */
function ClearAll({ count, onReset, className, t }: { count: number; onReset: () => void; className?: string; t: Translate }) {
    return (
        <Button variant="ghost" onClick={onReset} className={cn('text-muted-foreground hover:text-foreground h-9 shrink-0 px-2 font-normal', className)}>
            {fill(t(NS, 'clear_all_count', 'Tout effacer (:count)'), { count })}
        </Button>
    );
}
