import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { useTranslation } from '@/hooks/use-translation';
import { cn } from '@/lib/utils';
import type {
    LogementShowAvailability,
    LogementShowAvailabilityEvent,
    LogementShowLinks,
    LogementShowPlanning,
    LogementShowPlanningEvent,
} from '@/types/logement-show';
import { Link, router } from '@inertiajs/react';
import { AlertTriangle, ArrowRight, CalendarDays, SprayCan } from 'lucide-react';
import { Suspense, lazy, useEffect, useState, type ReactNode } from 'react';
import { AVAILABILITY_SWATCH_CLASSES } from '../../components/availability-colors';
import {
    PLANNING_SWATCH_CLASSES,
    availabilityToEvents,
    availabilityTooltipLines,
    calendarRange,
    planningToEvents,
    planningTooltipLines,
} from '../calendar-events';
import { NS } from '../format';
import { SectionTitle } from './detail-card';

// La bibliothèque de calendrier n'est téléchargée qu'à la première ouverture.
const MiniCalendar = lazy(() => import('./mini-calendar'));

type PropKey = 'availability' | 'planning';
type LoadStatus = 'idle' | 'loading' | 'error';

interface Props {
    links: LogementShowLinks;
    /** Absent tant que l'accordéon n'a pas été ouvert ; null sans droit. */
    availability?: LogementShowAvailability | null;
    planning?: LogementShowPlanning | null;
}

/** Au-delà, le calendrier attendu d'office est signalé comme lent, avec « Réessayer ». */
const SLOW_AFTER_MS = 10_000;

/**
 * « Calendrier et planning » : deux aperçus du mois. Le calendrier du logement
 * est ouvert d'office : sa propriété est DIFFÉRÉE côté serveur, et c'est le
 * client Inertia qui la réclame après le premier rendu — jamais un effet de ce
 * composant, qui partirait avant l'initialisation du routeur et se perdrait.
 * Le planning reste replié : propriété facultative, demandée par un
 * rechargement partiel à l'ouverture de son volet.
 */
export function CalendarsCard({ links, availability, planning }: Props) {
    const { t, locale } = useTranslation();
    const [status, setStatus] = useState<Record<PropKey, LoadStatus>>({ availability: 'idle', planning: 'idle' });
    const [slow, setSlow] = useState(false);

    const calendarHref = links.calendar;
    const planningHref = links.planning;

    const loaded: Record<PropKey, boolean> = { availability: availability !== undefined, planning: planning !== undefined };

    // La prop différée n'expose ni échec ni progression : passé un délai, on le
    // dit et on propose de la redemander, plutôt qu'un squelette sans fin.
    const awaitingCalendar = calendarHref !== null && availability === undefined;
    useEffect(() => {
        if (!awaitingCalendar) {
            return;
        }
        const timer = window.setTimeout(() => setSlow(true), SLOW_AFTER_MS);
        return () => window.clearTimeout(timer);
    }, [awaitingCalendar]);

    const load = (key: PropKey) => {
        if (loaded[key] || status[key] === 'loading') {
            return;
        }
        let settled = false;
        setStatus((current) => ({ ...current, [key]: 'loading' }));
        router.reload({
            only: [key],
            onSuccess: (page) => {
                // Une réponse sans la propriété (serveur pas à jour) est un échec, pas un chargement sans fin.
                settled = page.props[key] !== undefined;
                setStatus((current) => ({ ...current, [key]: settled ? 'idle' : 'error' }));
            },
            onFinish: () => {
                if (!settled) {
                    setStatus((current) => ({ ...current, [key]: 'error' }));
                }
            },
        });
    };

    if (!calendarHref && !planningHref) {
        return null;
    }

    const onValueChange = (value: string) => {
        if (value === 'availability' || value === 'planning') {
            load(value);
        }
    };

    const body = (key: PropKey, render: () => ReactNode) => {
        if (status[key] === 'error') {
            return (
                <div className="flex items-center justify-between gap-2 text-sm">
                    <span className="text-muted-foreground">{t(NS, 'load_failed')}</span>
                    <Button variant="outline" size="sm" onClick={() => load(key)}>
                        {t(NS, 'retry')}
                    </Button>
                </div>
            );
        }
        if (!loaded[key]) {
            return (
                <div className="flex flex-col gap-2">
                    {key === 'availability' && slow && status.availability === 'idle' && (
                        <div className="flex items-center justify-between gap-2 text-sm">
                            <span className="text-muted-foreground">{t(NS, 'calendar_slow')}</span>
                            <Button variant="outline" size="sm" onClick={() => load('availability')}>
                                {t(NS, 'retry')}
                            </Button>
                        </div>
                    )}
                    <CalendarSkeleton label={t(NS, 'loading')} />
                </div>
            );
        }
        return render();
    };

    return (
        <Card data-tour="logement-show-calendars" className="gap-0">
            <CardContent>
                <SectionTitle>{t(NS, 'calendars_title')}</SectionTitle>
                <Accordion type="single" collapsible defaultValue={calendarHref ? 'availability' : undefined} onValueChange={onValueChange}>
                    {calendarHref && (
                        <AccordionItem value="availability">
                            <AccordionTrigger className="[&>svg]:text-muted-foreground py-3 text-sm font-semibold hover:no-underline">
                                <span className="flex items-center gap-2">
                                    <CalendarDays className="text-muted-foreground h-4 w-4" aria-hidden="true" />
                                    {t(NS, 'calendar_item')}
                                </span>
                            </AccordionTrigger>
                            <AccordionContent>
                                {body('availability', () =>
                                    availability ? (
                                        <AvailabilityPanel
                                            availability={availability}
                                            locale={locale}
                                            calendarHref={calendarHref}
                                            manageHref={links.edit ? `${links.edit}?step=5` : null}
                                        />
                                    ) : (
                                        <p className="text-muted-foreground text-sm">{t(NS, 'unavailable')}</p>
                                    ),
                                )}
                            </AccordionContent>
                        </AccordionItem>
                    )}
                    {planningHref && (
                        <AccordionItem value="planning">
                            <AccordionTrigger className="[&>svg]:text-muted-foreground py-3 text-sm font-semibold hover:no-underline">
                                <span className="flex items-center gap-2">
                                    <SprayCan className="text-muted-foreground h-4 w-4" aria-hidden="true" />
                                    {t(NS, 'planning_item')}
                                </span>
                            </AccordionTrigger>
                            <AccordionContent>
                                {body('planning', () =>
                                    planning ? (
                                        <PlanningPanel planning={planning} locale={locale} planningHref={planningHref} />
                                    ) : (
                                        <p className="text-muted-foreground text-sm">{t(NS, 'unavailable')}</p>
                                    ),
                                )}
                            </AccordionContent>
                        </AccordionItem>
                    )}
                </Accordion>
            </CardContent>
        </Card>
    );
}

function CalendarSkeleton({ label }: { label: string }) {
    return (
        <div role="status" aria-label={label} className="flex flex-col gap-2">
            <Skeleton className="mx-auto h-5 w-32" />
            <Skeleton className="h-56 w-full" />
        </div>
    );
}

function AvailabilityPanel({
    availability,
    locale,
    calendarHref,
    manageHref,
}: {
    availability: LogementShowAvailability;
    locale: string;
    calendarHref: string;
    manageHref: string | null;
}) {
    const { t } = useTranslation();
    const open = () => router.visit(calendarHref);

    return (
        <div className="flex flex-col gap-3">
            {!availability.channel_read && (
                <p className="flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-2 text-xs text-amber-900 dark:border-amber-900/40 dark:bg-amber-950/30 dark:text-amber-200">
                    <AlertTriangle className="mt-px h-3.5 w-3.5 shrink-0" aria-hidden="true" />
                    {t(NS, 'calendar_channel_unread')}
                </p>
            )}
            <Suspense fallback={<CalendarSkeleton label={t(NS, 'loading')} />}>
                <MiniCalendar
                    events={availabilityToEvents(availability.events)}
                    validRange={calendarRange(availability.from_iso, availability.to_iso)}
                    locale={locale}
                    tooltip={(event) => availabilityTooltipLines(event.extendedProps.availability as LogementShowAvailabilityEvent, locale, t)}
                    onOpen={open}
                />
            </Suspense>
            <Legend
                items={[
                    { swatch: 'bg-card', label: t(NS, 'legend_available') },
                    { swatch: AVAILABILITY_SWATCH_CLASSES.reservation, label: t(NS, 'legend_reserved') },
                    { swatch: AVAILABILITY_SWATCH_CLASSES.ical, label: t(NS, 'legend_ical') },
                    { swatch: AVAILABILITY_SWATCH_CLASSES.manual_block, label: t(NS, 'legend_manual_block') },
                    { swatch: AVAILABILITY_SWATCH_CLASSES.channel_block, label: t(NS, 'legend_channel_block') },
                ]}
            />
            <Footer>
                <FooterLink href={calendarHref}>{t(NS, 'calendar_open')}</FooterLink>
                {manageHref && <FooterLink href={manageHref}>{t(NS, 'calendar_manage')}</FooterLink>}
            </Footer>
        </div>
    );
}

function PlanningPanel({ planning, locale, planningHref }: { planning: LogementShowPlanning; locale: string; planningHref: string }) {
    const { t } = useTranslation();

    return (
        <div className="flex flex-col gap-3">
            <Suspense fallback={<CalendarSkeleton label={t(NS, 'loading')} />}>
                <MiniCalendar
                    events={planningToEvents(planning.events, {
                        toAssign: t(NS, 'planning_card_to_assign', 'À assigner'),
                        arrival: t(NS, 'legend_arrival', 'Arrivée'),
                    })}
                    validRange={calendarRange(planning.from_iso, planning.to_iso)}
                    locale={locale}
                    tooltip={(event) => planningTooltipLines(event.extendedProps.planning as LogementShowPlanningEvent, t)}
                    onOpen={() => router.visit(planningHref)}
                />
            </Suspense>
            <Legend
                round
                items={[
                    { swatch: PLANNING_SWATCH_CLASSES.assigned, label: t(NS, 'legend_cleaning_assigned') },
                    { swatch: PLANNING_SWATCH_CLASSES.unassigned, label: t(NS, 'legend_cleaning_unassigned') },
                    { swatch: PLANNING_SWATCH_CLASSES.arrival, label: t(NS, 'legend_arrival') },
                ]}
            />
            <Footer>
                <FooterLink href={planningHref}>{t(NS, 'planning_open')}</FooterLink>
            </Footer>
        </div>
    );
}

function Legend({ items, round = false }: { items: { swatch: string; label: string }[]; round?: boolean }) {
    return (
        <ul className="text-muted-foreground flex flex-wrap gap-x-3 gap-y-1 text-xs">
            {items.map((item) => (
                <li key={item.label} className="flex items-center gap-1.5">
                    <span
                        className={cn('border-border inline-block h-3 w-3 border', round ? 'rounded-full' : 'rounded-[3px]', item.swatch)}
                        aria-hidden="true"
                    />
                    {item.label}
                </li>
            ))}
        </ul>
    );
}

function Footer({ children }: { children: ReactNode }) {
    return <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1">{children}</div>;
}

function FooterLink({ href, children }: { href: string; children: ReactNode }) {
    return (
        <Link href={href} className="text-primary dark:text-secondary inline-flex items-center gap-1 text-sm font-medium hover:underline">
            {children}
            <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
        </Link>
    );
}
