import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useTranslation } from '@/hooks/use-translation';
import { cn } from '@/lib/utils';
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router, usePage } from '@inertiajs/react';
import { Activity, Eye, Layers, Search } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { useCanManageYield, useYieldScope } from './hooks/useYieldPermissions';
import ApplyRuleGroupDialog from './components/ApplyRuleGroupDialog';
import BulkActionBar from './components/BulkActionBar';
import { FranchiseReadOnlyBanner, FranchiseSelect } from './components/FranchiseScope';
import PushNowDialog from './components/PushNowDialog';
import StartingRulesDialog from './components/StartingRulesDialog';
import YieldRuleSheet from './components/YieldRuleSheet';
import YieldTable from './components/YieldTable';
import { ambiguousListingUids } from './lib/pinned-layout';
import { DEMAND_LEGEND } from './lib/price-color';
import { latestRow } from './lib/push-preview';
import { scopeQuery } from './lib/yield-scope';
import { useYieldSeries } from './hooks/useYieldSeries';
import type { PriceDay, YieldRuleGroup, YieldScope, YieldTableRow } from './types';

const NS = 'yield.index';

interface PageProps {
    rows: YieldTableRow[];
    ruleGroups: YieldRuleGroup[];
    exchangeRate: number;
    yieldScope: YieldScope;
    [key: string]: unknown;
}

type StatusFilter = 'all' | 'active' | 'inactive' | 'unconfigured';
type GroupFilter = 'all' | 'with' | 'without';

const STATUS_FILTERS: StatusFilter[] = ['all', 'active', 'inactive', 'unconfigured'];

const GROUP_FILTERS: GroupFilter[] = ['all', 'with', 'without'];

type Translate = ReturnType<typeof useTranslation>['t'];

/*
 * Libellés des filtres : un appel `t(NS, 'clé', …)` LITTÉRAL par valeur, jamais
 * une clé fabriquée à la volée (`filter_status_` + valeur). YieldTranslationsTest
 * ne voit que les clés écrites en toutes lettres : une clé construite échappait
 * au contrôle de parité FR/EN, et sa disparition des deux fichiers de langue
 * serait passée sans qu'aucun test ne tombe.
 */
function statusFilterLabel(t: Translate, value: StatusFilter): string {
    switch (value) {
        case 'all':
            return t(NS, 'filter_status_all', 'Tous');
        case 'active':
            return t(NS, 'filter_status_active', 'Actifs');
        case 'inactive':
            return t(NS, 'filter_status_inactive', 'Inactifs');
        case 'unconfigured':
            return t(NS, 'filter_status_unconfigured', 'Non configurés');
    }
}

function groupFilterLabel(t: Translate, value: GroupFilter): string {
    switch (value) {
        case 'all':
            return t(NS, 'filter_group_all', 'Tous');
        case 'with':
            return t(NS, 'filter_group_with', 'Avec un groupe');
        case 'without':
            return t(NS, 'filter_group_without', 'Sans groupe');
    }
}

/** Rang d'affichage d'une ligne : piloté (0) → configuré mais éteint (1) → jamais touché (2). */
const rangAffichage = (r: YieldTableRow): number => (r.is_active ? 0 : r.has_config ? 1 : 2);

export default function YieldIndex() {
    const { rows, ruleGroups, exchangeRate } = usePage<PageProps>().props;
    const { t } = useTranslation();
    const canManage = useCanManageYield();
    // `?coHost=` : le compte principal consulte une franchise (lecture seule).
    // Relayé sur chaque navigation, sinon « Historique » rouvrirait le parc du siège.
    const { coHost, readOnly } = useYieldScope();
    const scope = scopeQuery(coHost);

    const [search, setSearch] = useState('');
    const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
    const [groupFilter, setGroupFilter] = useState<GroupFilter>('all');
    const [selected, setSelected] = useState<Set<string>>(new Set());
    const [dialogOpen, setDialogOpen] = useState(false);
    const [dialogTargets, setDialogTargets] = useState<string[]>([]);
    const [sheetOpen, setSheetOpen] = useState(false);
    const [sheetConfigId, setSheetConfigId] = useState<number | null>(null);
    const [applyGroupOpen, setApplyGroupOpen] = useState(false);
    const [applyGroupTargets, setApplyGroupTargets] = useState<string[]>([]);
    const [pushOpen, setPushOpen] = useState(false);
    const [pushRow, setPushRow] = useState<YieldTableRow | null>(null);
    /** L'étape sur laquelle la fenêtre s'ouvre : le comparatif, ou directement la restauration. */
    const [pushStep, setPushStep] = useState<'preview' | 'restore'>('preview');
    // La fenêtre lit la ligne À JOUR, pas la copie prise au clic : une
    // restauration en échec a déjà éteint le yield côté serveur, et la fenêtre
    // doit le dire (cf. latestRow).
    const pushRowAJour = useMemo(() => latestRow(rows, pushRow), [rows, pushRow]);

    const breadcrumbs: BreadcrumbItem[] = [{ title: t(NS, 'breadcrumb_yield', 'Yield Manager'), href: route('yield.index', scope) }];

    // Lazy-hydrate the day-grid: chunk + POST yield.calendar for configured rows.
    const configuredUids = useMemo(() => rows.filter((r) => r.has_config).map((r) => r.uid), [rows]);

    // Libellés portés par plusieurs logements, sur TOUT le parc (pas la liste
    // filtrée) : un logement s'affiche de la même façon quel que soit le filtre.
    const ambiguousNames = useMemo(() => ambiguousListingUids(rows), [rows]);
    const { series, setSeriesForUid, refreshUid } = useYieldSeries(configuredUids);

    const filtered = useMemo(() => {
        const q = search.trim().toLowerCase();
        const visibles = rows.filter((r) => {
            if (q && !(r.name.toLowerCase().includes(q) || (r.internal_name ?? '').toLowerCase().includes(q) || r.uid.toLowerCase().includes(q))) {
                return false;
            }
            if (statusFilter === 'active' && !r.is_active) return false;
            if (statusFilter === 'inactive' && !(r.has_config && !r.is_active)) return false;
            if (statusFilter === 'unconfigured' && r.has_config) return false;
            if (groupFilter === 'with' && r.rule_group_id == null) return false;
            if (groupFilter === 'without' && r.rule_group_id != null) return false;
            return true;
        });

        // Les logements PILOTÉS remontent en tête, puis ceux qui sont configurés
        // mais éteints, puis ceux auxquels on n'a jamais touché : ce sont les
        // premiers qu'on vient surveiller, et les seuls dont la grille de prix
        // est renseignée. `sort` est stable en JS moderne, donc l'ordre
        // alphabétique rendu par le serveur est conservé DANS chaque groupe.
        return visibles.sort((a, b) => rangAffichage(a) - rangAffichage(b));
    }, [rows, search, statusFilter, groupFilter]);

    const toggleRow = useCallback((uid: string) => {
        setSelected((prev) => {
            const next = new Set(prev);
            if (next.has(uid)) {
                next.delete(uid);
            } else {
                next.add(uid);
            }
            return next;
        });
    }, []);

    const selectAllFiltered = useCallback(() => {
        setSelected((prev) => {
            const next = new Set(prev);
            filtered.forEach((r) => next.add(r.uid));
            return next;
        });
    }, [filtered]);

    const clearSelection = useCallback(() => setSelected(new Set()), []);

    const openSingleActivate = useCallback((uid: string) => {
        setDialogTargets([uid]);
        setDialogOpen(true);
    }, []);

    const openBulkActivate = () => {
        setDialogTargets(Array.from(selected));
        setDialogOpen(true);
    };

    // Only CONFIGURED rows can follow a group (a group carries no prices).
    const openApplyGroup = useCallback(() => {
        const configured = rows.filter((r) => selected.has(r.uid) && r.has_config).map((r) => r.uid);
        setApplyGroupTargets(configured);
        setApplyGroupOpen(true);
    }, [rows, selected]);

    const openConfigure = useCallback((configId: number) => {
        setSheetConfigId(configId);
        setSheetOpen(true);
    }, []);

    /**
     * Le comparatif d'envoi vit ICI, en un seul exemplaire : monter une fenêtre
     * par ligne coûterait 124 dialogues montés pour un seul jamais ouvert.
     */
    const openPushNow = useCallback((row: YieldTableRow) => {
        setPushRow(row);
        setPushStep('preview');
        setPushOpen(true);
    }, []);

    /**
     * Ouvre la MÊME fenêtre, directement sur l'étape de restauration. Le geste
     * part de la ligne, où le bouton se voit, plutôt que d'obliger à passer par
     * le comparatif d'envoi pour trouver comment revenir en arrière.
     */
    const openRestore = useCallback((row: YieldTableRow) => {
        setPushRow(row);
        setPushStep('restore');
        setPushOpen(true);
    }, []);

    /**
     * Après un envoi (ou une restauration), la grille du logement montre encore
     * les prix d'AVANT : on la relit tout de suite, pour que la ligne affiche
     * ce qui vient réellement de partir chez Rentals United.
     */
    const handlePushed = useCallback((uid: string) => void refreshUid(uid), [refreshUid]);

    // Live preview behind the slide-over: overwrite that row's day-cells.
    const handlePreview = useCallback(
        (uid: string, preview: PriceDay[]) => setSeriesForUid(uid, preview),
        [setSeriesForUid],
    );

    // On save, re-fetch the persisted/clamped series for the edited config's uid.
    const handleSaved = useCallback(() => {
        const edited = rows.find((r) => r.id === sheetConfigId);
        if (edited) void refreshUid(edited.uid);
    }, [rows, sheetConfigId, refreshUid]);

    /**
     * Après une activation, la grille de ces logements n'a JAMAIS été demandée :
     * ils n'avaient pas de configuration, ils étaient donc hors du lot hydraté
     * au chargement. Sans cet appel, leurs cellules restent vides jusqu'à un
     * rechargement complet de la page — on va donc chercher leurs prix tout de
     * suite, pour que l'aperçu apparaisse à l'instant où l'activation réussit.
     */
    const handleActivated = useCallback(() => {
        const actives = dialogTargets;
        clearSelection();
        actives.forEach((uid) => void refreshUid(uid));
    }, [dialogTargets, clearSelection, refreshUid]);

    const selectedRows = useMemo(() => rows.filter((r) => selected.has(r.uid)), [rows, selected]);

    // Parmi les logements visés par « Règles de départ », ceux qui ont déjà une
    // config : ils gardent leurs règles tant que « Conserver » reste coché.
    const dialogConfiguredCount = useMemo(() => {
        const targets = new Set(dialogTargets);
        return rows.filter((r) => targets.has(r.uid) && r.has_config).length;
    }, [rows, dialogTargets]);

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title={t(NS, 'title', 'Yield Manager')} />
            <div className="flex flex-col gap-4 p-3 sm:p-4 lg:p-6">
                <div className="flex flex-wrap items-center justify-between gap-3">
                    <h1 className="text-2xl font-bold">{t(NS, 'title', 'Yield Manager')}</h1>
                    <div className="flex flex-wrap items-center gap-2">
                        {/* Compte principal seulement : rien n'est rendu pour une franchise. */}
                        <FranchiseSelect routeName="yield.index" />
                        <div className="flex items-center gap-2" data-tour="yield-nav">
                            <Button variant="outline" onClick={() => router.visit(route('yield.rule-groups.index', scope))}>
                                <Layers className="h-4 w-4" />
                                {t(NS, 'rule_groups', 'Groupes de règles')}
                            </Button>
                            <Button variant="outline" onClick={() => router.visit(route('yield.logs', scope))}>
                                <Activity className="h-4 w-4" />
                                {t(NS, 'history', 'Historique')}
                            </Button>
                        </div>
                    </div>
                </div>

                {/* Le siège consulte une franchise : tout est éteint, et le
                    bandeau dit pourquoi (ce n'est pas une question de droits). */}
                <FranchiseReadOnlyBanner />

                {/* Lecture seule : le rôle « gérant » a `yield.view` sans
                    `yield.manage`, car écrire ici publie des prix sur Airbnb /
                    Booking via Rentals United. On l'ANNONCE au lieu de laisser
                    l'utilisateur découvrir des 403 un par un. */}
                {!canManage && !readOnly && (
                    <div className="flex items-center gap-2 rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-900/40 dark:bg-amber-950/30 dark:text-amber-200">
                        <Eye className="h-4 w-4 shrink-0" />
                        <span>{t(NS, 'read_only_banner', 'Lecture seule : vous pouvez consulter la tarification, mais pas la modifier. Les prix du Yield Manager partent sur les annonces Airbnb / Booking.')}</span>
                    </div>
                )}

                <div className="flex flex-wrap items-center gap-3">
                    <div className="relative max-w-sm flex-1">
                        <Search className="text-muted-foreground absolute top-2.5 left-2 h-4 w-4" />
                        <Input
                            className="pl-8"
                            placeholder={t(NS, 'search_placeholder', 'Rechercher un logement…')}
                            value={search}
                            onChange={(e) => setSearch(e.target.value)}
                        />
                    </div>

                    {/* Les deux filtres segmentés, regroupés pour la visite guidée :
                        ils se lisent ensemble (statut × groupe). */}
                    <div className="flex flex-wrap items-center gap-3" data-tour="yield-filters">
                        {/* Status segmented filter */}
                        <div className="bg-muted/50 flex items-center gap-0.5 rounded-lg p-0.5">
                            {STATUS_FILTERS.map((value) => (
                                <button
                                    key={value}
                                    type="button"
                                    onClick={() => setStatusFilter(value)}
                                    className={cn(
                                        'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
                                        statusFilter === value ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
                                    )}
                                >
                                    {statusFilterLabel(t, value)}
                                </button>
                            ))}
                        </div>

                        {/* Rule-group segmented filter */}
                        <div className="bg-muted/50 flex items-center gap-0.5 rounded-lg p-0.5">
                            {GROUP_FILTERS.map((value) => (
                                <button
                                    key={value}
                                    type="button"
                                    onClick={() => setGroupFilter(value)}
                                    className={cn(
                                        'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
                                        groupFilter === value ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',
                                    )}
                                >
                                    {groupFilterLabel(t, value)}
                                </button>
                            ))}
                        </div>
                    </div>
                </div>

                <YieldTable
                    rows={filtered}
                    exchangeRate={exchangeRate}
                    selected={selected}
                    series={series}
                    ruleGroups={ruleGroups}
                    onToggleRow={toggleRow}
                    onSelectAll={selectAllFiltered}
                    onClearAll={clearSelection}
                    onActivateSingle={openSingleActivate}
                    onConfigure={openConfigure}
                    onPushNow={openPushNow}
                    onRestore={openRestore}
                    ambiguousNames={ambiguousNames}
                />

                {/* Demand legend */}
                <div className="text-muted-foreground flex flex-wrap items-center gap-3 text-[11px]">
                    <span className="font-medium">{t(NS, 'legend_title', 'Demande :')}</span>
                    {DEMAND_LEGEND.map((item) => (
                        <span key={item.label} className="flex items-center gap-1">
                            <span className={`h-3 w-3 rounded border ${item.className}`} />
                            {item.label}
                        </span>
                    ))}
                </div>
            </div>

            <BulkActionBar selectedRows={selectedRows} onActivate={openBulkActivate} onApplyGroup={openApplyGroup} onClear={clearSelection} />

            <StartingRulesDialog
                open={dialogOpen}
                onOpenChange={setDialogOpen}
                targetUids={dialogTargets}
                ruleGroups={ruleGroups}
                onActivated={handleActivated}
                configuredCount={dialogConfiguredCount}
            />

            <ApplyRuleGroupDialog
                open={applyGroupOpen}
                onOpenChange={setApplyGroupOpen}
                groups={ruleGroups}
                targetUids={applyGroupTargets}
                onApplied={clearSelection}
            />

            <PushNowDialog open={pushOpen} onOpenChange={setPushOpen} row={pushRowAJour} onPushed={handlePushed} etapeInitiale={pushStep} />

            <YieldRuleSheet
                open={sheetOpen}
                onOpenChange={setSheetOpen}
                configId={sheetConfigId}
                exchangeRate={exchangeRate}
                onPreview={handlePreview}
                onSaved={handleSaved}
            />
        </AppLayout>
    );
}
