// Design: Corporate Modernism — Itinova Budget SI
// OPEX DSI : vignettes par catégorie, CRUD lignes, persistance par année, validation définitive

import { useState, useMemo, useEffect, useCallback } from 'react';
import {
  TrendingDown,
  TrendingUp,
  Search,
  ChevronDown,
  ChevronUp,
  Info,
  BarChart3,
  Building2,
  Euro,
  Filter,
  Eye,
  EyeOff,
  ArrowUpDown,
  Save,
  Lock,
  CheckCircle2,
  PencilLine,
  RotateCcw,
  Plus,
  Pencil,
  Trash2,
  X,
} from 'lucide-react';
import { AppSidebar } from '../components/AppSidebar';
import { AnneeSelectorBar } from '../components/AnneeSelectorBar';
import { useAnnee, getOpexStorageKey } from '../contexts/AnneeContext';
import opexRaw from '../data_opex.json';
import opex2025Raw from '../data_opex_2025.json';
import { formatEuros } from '../lib/format';
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogFooter,
} from '@/components/ui/dialog';
import { toast } from 'sonner';

// ─── Types ────────────────────────────────────────────────────────────────────

interface Poste {
  col_idx: number;
  fournisseur: string | null;
  libelle: string;
  detail: string | null;
  facturation: string | null;
  mode_ventilation: string | null;
  categorie: string | null;
  type: string | null;
  compte: string | null;
  budget_n1: number | null;
  montant_previsionnel_2026?: number | null;
  montant_previsionnel_2025?: number | null;
}

interface Etablissement {
  code: string;
  nom: string;
  base_repartition: number;
  montants: Record<string, number>;
  total: number;
}

interface TendanceCategorie {
  montant_n1: number | null;
  montant_n: number;
  variation_pct: number | null;
  tendance: 'hausse' | 'baisse' | 'stable' | 'new';
}

interface OpexData {
  annee: number;
  total_global: number;
  postes: Poste[];
  etablissements: Etablissement[];
  categories_totaux: Record<string, number>;
  tendances_categories?: Record<string, TendanceCategorie>;
  meta: { nb_postes: number; nb_etablissements?: number };
}

// Ligne persistée (peut être source ou ajoutée par l'utilisateur)
interface LigneOpex {
  id: string;           // identifiant stable
  libelle: string;
  fournisseur: string;
  categorie: string;
  type: string;
  facturation: string;
  compte: string;
  detail: string;
  budget_n1: number;
  montant: number;      // montant prévisionnel éditable
  isCustom: boolean;    // true = ajoutée par l'utilisateur
}

interface OpexAnneeState {
  lignes: LigneOpex[];
  validated: boolean;
  savedAt?: string;
  validatedAt?: string;
}

// ─── Constantes ───────────────────────────────────────────────────────────────

const opexData2026 = opexRaw as OpexData;
const opexData2025 = opex2025Raw as OpexData;

function getOpexDataForAnnee(annee: number): OpexData {
  if (annee === 2025) return opexData2025;
  return opexData2026; // 2026 et autres années
}

const CATEGORIES_FIXES = ['App global', 'Infogérance', 'Sécurité', 'Téléphonie', 'App HEP', 'App SMR'];

const CATEGORIE_COLORS: Record<string, { badge: string; bar: string; kpi: string; icon: string }> = {
  'App global':   { badge: 'bg-blue-100 text-blue-700',    bar: 'bg-blue-500',    kpi: 'text-blue-600',    icon: 'text-blue-500' },
  'Infogérance':  { badge: 'bg-purple-100 text-purple-700', bar: 'bg-purple-500',  kpi: 'text-purple-600',  icon: 'text-purple-500' },
  'Sécurité':     { badge: 'bg-red-100 text-red-700',       bar: 'bg-red-500',     kpi: 'text-red-600',     icon: 'text-red-500' },
  'Téléphonie':   { badge: 'bg-green-100 text-green-700',   bar: 'bg-green-500',   kpi: 'text-green-600',   icon: 'text-green-500' },
  'App HEP':      { badge: 'bg-orange-100 text-orange-700', bar: 'bg-orange-500',  kpi: 'text-orange-600',  icon: 'text-orange-500' },
  'App SMR':      { badge: 'bg-teal-100 text-teal-700',     bar: 'bg-teal-500',    kpi: 'text-teal-600',    icon: 'text-teal-500' },
  'Autre':        { badge: 'bg-gray-100 text-gray-600',     bar: 'bg-gray-400',    kpi: 'text-gray-600',    icon: 'text-gray-400' },
};

function getCatColors(cat: string) {
  return CATEGORIE_COLORS[cat] ?? CATEGORIE_COLORS['Autre'];
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function formatNum(v: number | null | undefined): string {
  if (v === null || v === undefined || v === 0) return '—';
  return new Intl.NumberFormat('fr-FR', { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(v) + ' €';
}

function buildLignesFromSource(annee?: number): LigneOpex[] {
  const src = getOpexDataForAnnee(annee ?? 2026);
  return src.postes.map(p => ({
    id: `src-${p.col_idx}`,
    libelle: p.libelle,
    fournisseur: p.fournisseur ?? '',
    categorie: p.categorie ?? 'Autre',
    type: p.type ?? '',
    facturation: p.facturation ?? '',
    compte: p.compte ?? '',
    detail: p.detail ?? '',
    budget_n1: p.budget_n1 ?? 0,
    montant: (annee === 2025 ? p.montant_previsionnel_2025 : p.montant_previsionnel_2026) ?? 0,
    isCustom: false,
  }));
}

function loadOpexState(annee: number): OpexAnneeState {
  try {
    const raw = localStorage.getItem(getOpexStorageKey(annee));
    if (raw) {
      const parsed = JSON.parse(raw) as OpexAnneeState;
      if (parsed.lignes && Array.isArray(parsed.lignes)) return parsed;
    }
  } catch { /* ignore */ }
  // Données sources disponibles pour 2025 et 2026 uniquement
  if (annee === 2025 || annee === 2026) {
    return { lignes: buildLignesFromSource(annee), validated: false };
  }
  return { lignes: [], validated: false };
}

function saveOpexState(annee: number, state: OpexAnneeState): void {
  try {
    localStorage.setItem(getOpexStorageKey(annee), JSON.stringify(state));
  } catch { /* ignore */ }
}

function genId(): string {
  return `custom-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
}

// ─── Composant champ numérique ─────────────────────────────────────────────

function MoneyInput({
  value,
  onChange,
  disabled = false,
}: {
  value: number;
  onChange: (v: number) => void;
  disabled?: boolean;
}) {
  const [raw, setRaw] = useState(value === 0 ? '' : String(value));

  useEffect(() => {
    setRaw(value === 0 ? '' : String(value));
  }, [value]);

  return (
    <div className="relative">
      <input
        type="number"
        min={0}
        step={100}
        value={raw}
        placeholder="0"
        disabled={disabled}
        onChange={e => {
          setRaw(e.target.value);
          const v = parseFloat(e.target.value);
          onChange(isNaN(v) || v < 0 ? 0 : Math.round(v));
        }}
        onBlur={() => setRaw(value === 0 ? '' : String(value))}
        className={`w-full pl-6 pr-2 py-1.5 text-xs border rounded-md focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary tabular-nums transition-colors text-right ${
          disabled
            ? 'bg-muted/30 text-muted-foreground cursor-not-allowed border-border/40'
            : 'bg-background border-border hover:border-primary/50'
        }`}
      />
      <span className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground text-xs">€</span>
    </div>
  );
}

// ─── Formulaire ajout/édition ──────────────────────────────────────────────

const EMPTY_LIGNE: Omit<LigneOpex, 'id' | 'isCustom'> = {
  libelle: '',
  fournisseur: '',
  categorie: 'App global',
  type: '',
  facturation: '',
  compte: '',
  detail: '',
  budget_n1: 0,
  montant: 0,
};

function LigneForm({
  initial,
  onSave,
  onClose,
}: {
  initial: Omit<LigneOpex, 'id' | 'isCustom'>;
  onSave: (data: Omit<LigneOpex, 'id' | 'isCustom'>) => void;
  onClose: () => void;
}) {
  const [form, setForm] = useState(initial);
  const set = (k: keyof typeof form, v: string | number) =>
    setForm(prev => ({ ...prev, [k]: v }));

  const allCats = [...CATEGORIES_FIXES, 'Autre'];

  return (
    <div className="space-y-4">
      <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
        <div className="sm:col-span-2">
          <label className="block text-xs font-medium text-muted-foreground mb-1">Libellé du poste *</label>
          <input
            type="text"
            value={form.libelle}
            onChange={e => set('libelle', e.target.value)}
            placeholder="Ex : Maintenance serveurs"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Fournisseur</label>
          <input
            type="text"
            value={form.fournisseur}
            onChange={e => set('fournisseur', e.target.value)}
            placeholder="Ex : Microsoft"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Catégorie *</label>
          <select
            value={form.categorie}
            onChange={e => set('categorie', e.target.value)}
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 bg-background"
          >
            {allCats.map(c => <option key={c} value={c}>{c}</option>)}
          </select>
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Type</label>
          <input
            type="text"
            value={form.type}
            onChange={e => set('type', e.target.value)}
            placeholder="Ex : Licence, Maintenance…"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Facturation</label>
          <input
            type="text"
            value={form.facturation}
            onChange={e => set('facturation', e.target.value)}
            placeholder="Ex : Annuelle, Mensuelle…"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Compte comptable</label>
          <input
            type="text"
            value={form.compte}
            onChange={e => set('compte', e.target.value)}
            placeholder="Ex : 6156"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Budget N-1 (€)</label>
          <input
            type="number"
            min={0}
            value={form.budget_n1 || ''}
            onChange={e => set('budget_n1', parseFloat(e.target.value) || 0)}
            placeholder="0"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div>
          <label className="block text-xs font-medium text-muted-foreground mb-1">Montant prévisionnel (€) *</label>
          <input
            type="number"
            min={0}
            value={form.montant || ''}
            onChange={e => set('montant', parseFloat(e.target.value) || 0)}
            placeholder="0"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30"
          />
        </div>
        <div className="sm:col-span-2">
          <label className="block text-xs font-medium text-muted-foreground mb-1">Détail / Description</label>
          <textarea
            value={form.detail}
            onChange={e => set('detail', e.target.value)}
            rows={2}
            placeholder="Description complémentaire…"
            className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 resize-none"
          />
        </div>
      </div>
      <DialogFooter className="gap-2">
        <button
          onClick={onClose}
          className="px-4 py-2 text-sm rounded-lg border border-border bg-card hover:bg-muted transition-colors text-muted-foreground"
        >
          Annuler
        </button>
        <button
          onClick={() => {
            if (!form.libelle.trim()) { toast.error('Le libellé est obligatoire'); return; }
            onSave(form);
          }}
          className="px-4 py-2 text-sm rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 font-medium transition-all shadow-sm"
        >
          Enregistrer
        </button>
      </DialogFooter>
    </div>
  );
}

// ─── Composant principal ──────────────────────────────────────────────────────

type ViewMode = 'postes' | 'etablissements';
type SortDir = 'asc' | 'desc';

export default function DsiOpex() {
  const { annee } = useAnnee();

  const [opexState, setOpexState] = useState<OpexAnneeState>(() => loadOpexState(annee));
  const [isDirty, setIsDirty] = useState(false);

  // UI
  const [viewMode, setViewMode] = useState<ViewMode>('postes');
  const [search, setSearch] = useState('');
  const [selectedCategorie, setSelectedCategorie] = useState('Toutes');
  const [sortCol, setSortCol] = useState<string | null>(null);
  const [sortDir, setSortDir] = useState<SortDir>('desc');
  const [expandedPoste, setExpandedPoste] = useState<string | null>(null);
  const [showDetails, setShowDetails] = useState(false);

  // CRUD
  const [dialogMode, setDialogMode] = useState<'add' | 'edit' | null>(null);
  const [editingLigne, setEditingLigne] = useState<LigneOpex | null>(null);
  const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);

  // Recharger quand l'année change
  useEffect(() => {
    setOpexState(loadOpexState(annee));
    setIsDirty(false);
    setSearch('');
    setExpandedPoste(null);
  }, [annee]);

  const isValidated = opexState.validated;

  // Catégories présentes dans les lignes
  const categoriesPresentes = useMemo(() => {
    const cats = Array.from(new Set(opexState.lignes.map(l => l.categorie)));
    return CATEGORIES_FIXES.filter(c => cats.includes(c)).concat(
      cats.filter(c => !CATEGORIES_FIXES.includes(c) && c !== 'Autre')
    ).concat(cats.includes('Autre') ? ['Autre'] : []);
  }, [opexState.lignes]);

  const CATEGORIES_FILTER = useMemo(() => ['Toutes', ...categoriesPresentes], [categoriesPresentes]);

  // Modifier un montant directement dans le tableau
  const handleMontantChange = useCallback((id: string, value: number) => {
    if (isValidated) return;
    setOpexState(prev => ({
      ...prev,
      lignes: prev.lignes.map(l => l.id === id ? { ...l, montant: value } : l),
    }));
    setIsDirty(true);
  }, [isValidated]);

  // Enregistrer
  const handleSave = useCallback(() => {
    const newState = { ...opexState, savedAt: new Date().toISOString() };
    setOpexState(newState);
    saveOpexState(annee, newState);
    setIsDirty(false);
    toast.success(`OPEX ${annee} enregistré`, { description: 'Les montants prévisionnels ont été sauvegardés.' });
  }, [opexState, annee]);

  // Réinitialiser aux valeurs du JSON source
  const handleReset = useCallback(() => {
    if (isValidated) return;
    const newState: OpexAnneeState = { lignes: buildLignesFromSource(annee), validated: false };
    setOpexState(newState);
    saveOpexState(annee, newState);
    setIsDirty(false);
    toast.info('Montants réinitialisés', { description: 'Les valeurs ont été restaurées depuis les données sources.' });
  }, [isValidated, annee]);

  // Valider définitivement
  const handleValidate = useCallback(() => {
    const newState: OpexAnneeState = {
      ...opexState,
      validated: true,
      savedAt: new Date().toISOString(),
      validatedAt: new Date().toISOString(),
    };
    setOpexState(newState);
    saveOpexState(annee, newState);
    setIsDirty(false);
    toast.success(`OPEX ${annee} validé`, {
      description: 'Le prévisionnel est maintenant verrouillé et ne peut plus être modifié.',
    });
  }, [opexState, annee]);

  // Ajouter une ligne
  const handleAddLigne = useCallback((data: Omit<LigneOpex, 'id' | 'isCustom'>) => {
    const newLigne: LigneOpex = { ...data, id: genId(), isCustom: true };
    const newState = {
      ...opexState,
      lignes: [...opexState.lignes, newLigne],
    };
    setOpexState(newState);
    saveOpexState(annee, newState);
    setIsDirty(false);
    setDialogMode(null);
    toast.success('Ligne ajoutée');
  }, [opexState, annee]);

  // Modifier une ligne
  const handleEditLigne = useCallback((data: Omit<LigneOpex, 'id' | 'isCustom'>) => {
    if (!editingLigne) return;
    const newState = {
      ...opexState,
      lignes: opexState.lignes.map(l =>
        l.id === editingLigne.id ? { ...l, ...data } : l
      ),
    };
    setOpexState(newState);
    saveOpexState(annee, newState);
    setIsDirty(false);
    setDialogMode(null);
    setEditingLigne(null);
    toast.success('Ligne modifiée');
  }, [opexState, annee, editingLigne]);

  // Supprimer une ligne
  const handleDeleteLigne = useCallback((id: string) => {
    const newState = {
      ...opexState,
      lignes: opexState.lignes.filter(l => l.id !== id),
    };
    setOpexState(newState);
    saveOpexState(annee, newState);
    setIsDirty(false);
    setDeleteConfirmId(null);
    toast.success('Ligne supprimée');
  }, [opexState, annee]);

  // Totaux
  const totalGlobal = useMemo(() =>
    opexState.lignes.reduce((s, l) => s + l.montant, 0),
  [opexState.lignes]);

  const totalParCategorie = useMemo(() => {
    const totaux: Record<string, number> = {};
    for (const ligne of opexState.lignes) {
      const cat = ligne.categorie || 'Autre';
      totaux[cat] = (totaux[cat] || 0) + ligne.montant;
    }
    return totaux;
  }, [opexState.lignes]);

  // Filtrer les lignes
  const filteredLignes = useMemo(() => {
    return opexState.lignes.filter(l => {
      if (selectedCategorie !== 'Toutes' && l.categorie !== selectedCategorie) return false;
      if (search && !l.libelle.toLowerCase().includes(search.toLowerCase()) &&
          !l.fournisseur.toLowerCase().includes(search.toLowerCase())) return false;
      return true;
    });
  }, [opexState.lignes, selectedCategorie, search]);

  const totalFiltre = useMemo(() =>
    filteredLignes.reduce((s, l) => s + l.montant, 0),
  [filteredLignes]);

  // Trier les lignes
  const sortedLignes = useMemo(() => {
    const arr = [...filteredLignes];
    if (sortCol === 'montant') {
      arr.sort((a, b) => sortDir === 'asc' ? a.montant - b.montant : b.montant - a.montant);
    } else if (sortCol === 'libelle') {
      arr.sort((a, b) => sortDir === 'asc'
        ? a.libelle.localeCompare(b.libelle)
        : b.libelle.localeCompare(a.libelle));
    }
    return arr;
  }, [filteredLignes, sortCol, sortDir]);

  const handleSort = (col: string) => {
    if (sortCol === col) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
    else { setSortCol(col); setSortDir('desc'); }
  };

  const SortBtn = ({ col, label }: { col: string; label: string }) => (
    <button
      onClick={() => handleSort(col)}
      className="flex items-center gap-1 hover:text-foreground transition-colors group"
    >
      {label}
      <ArrowUpDown className={`w-3 h-3 ${sortCol === col ? 'text-primary' : 'text-muted-foreground/50 group-hover:text-muted-foreground'}`} />
    </button>
  );

  // Établissements (vue par établissement)
  const filteredEtabs = useMemo(() => {
    const srcData = getOpexDataForAnnee(annee);
    return srcData.etablissements.filter(e => {
      if (search && !e.nom.toLowerCase().includes(search.toLowerCase()) &&
          !e.code.toLowerCase().includes(search.toLowerCase())) return false;
      return true;
    });
  }, [search]);

  const sortedEtabs = useMemo(() => {
    const arr = [...filteredEtabs];
    if (sortCol === 'total') arr.sort((a, b) => sortDir === 'asc' ? a.total - b.total : b.total - a.total);
    else if (sortCol === 'nom') arr.sort((a, b) => sortDir === 'asc' ? a.nom.localeCompare(b.nom) : b.nom.localeCompare(a.nom));
    return arr;
  }, [filteredEtabs, sortCol, sortDir]);

  return (
    <div className="min-h-screen flex bg-background">
      <AppSidebar />

      <main className="flex-1 flex flex-col min-w-0 overflow-hidden">
        {/* En-tête */}
        <header className="bg-card border-b border-border px-6 py-4 flex-shrink-0">
          <div className="flex items-center justify-between gap-4 flex-wrap">
            <div>
              <div className="flex items-center gap-3">
                <h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>
                  OPEX DSI — Charges {annee}
                </h1>
                {isValidated && (
                  <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700 border border-emerald-200">
                    <Lock className="w-3 h-3" />
                    Validé le {opexState.validatedAt ? new Date(opexState.validatedAt).toLocaleDateString('fr-FR') : ''}
                  </span>
                )}
                {!isValidated && isDirty && (
                  <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold bg-amber-100 text-amber-700 border border-amber-200">
                    <PencilLine className="w-3 h-3" />
                    Modifications non enregistrées
                  </span>
                )}
              </div>
              <p className="text-sm text-muted-foreground mt-0.5">
                {opexState.lignes.length} postes de charges · {getOpexDataForAnnee(annee).meta.nb_etablissements ?? opexData2026.meta.nb_etablissements} établissements
                {opexState.savedAt && !isValidated && (
                  <span className="ml-2 text-xs">· Enregistré le {new Date(opexState.savedAt).toLocaleDateString('fr-FR')} à {new Date(opexState.savedAt).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}</span>
                )}
              </p>
            </div>
            <div className="flex items-center gap-3 flex-wrap">
              <AnneeSelectorBar />
              {!isValidated && (
                <>
                  <button
                    onClick={handleReset}
                    className="flex items-center gap-1.5 px-3 py-2 text-sm rounded-lg border border-border bg-card hover:bg-muted transition-colors text-muted-foreground"
                    title="Réinitialiser aux valeurs sources"
                  >
                    <RotateCcw className="w-3.5 h-3.5" />
                    Réinitialiser
                  </button>
                  <button
                    onClick={handleSave}
                    disabled={!isDirty}
                    className={`flex items-center gap-1.5 px-4 py-2 text-sm rounded-lg font-medium transition-all ${
                      isDirty
                        ? 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm'
                        : 'bg-muted text-muted-foreground cursor-not-allowed'
                    }`}
                  >
                    <Save className="w-3.5 h-3.5" />
                    Enregistrer
                  </button>
                  <AlertDialog>
                    <AlertDialogTrigger asChild>
                      <button className="flex items-center gap-1.5 px-4 py-2 text-sm rounded-lg font-medium bg-emerald-600 text-white hover:bg-emerald-700 transition-all shadow-sm">
                        <CheckCircle2 className="w-3.5 h-3.5" />
                        Valider
                      </button>
                    </AlertDialogTrigger>
                    <AlertDialogContent>
                      <AlertDialogHeader>
                        <AlertDialogTitle>Valider l'OPEX {annee} ?</AlertDialogTitle>
                        <AlertDialogDescription>
                          Cette action est <strong>irréversible</strong>. Une fois validé, le prévisionnel OPEX {annee} sera verrouillé et ne pourra plus être modifié.
                        </AlertDialogDescription>
                      </AlertDialogHeader>
                      <AlertDialogFooter>
                        <AlertDialogCancel>Annuler</AlertDialogCancel>
                        <AlertDialogAction onClick={handleValidate} className="bg-emerald-600 hover:bg-emerald-700 text-white">
                          Confirmer la validation
                        </AlertDialogAction>
                      </AlertDialogFooter>
                    </AlertDialogContent>
                  </AlertDialog>
                </>
              )}
              {isValidated && (
                <div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-emerald-50 border border-emerald-200 text-emerald-700 text-sm">
                  <Lock className="w-4 h-4" />
                  <span className="font-medium">Prévisionnel verrouillé</span>
                </div>
              )}
            </div>
          </div>
        </header>

        {/* KPIs — vignette Total + une par catégorie */}
        <div className="px-6 py-4 border-b border-border bg-muted/20 flex-shrink-0">
          <div className="flex flex-wrap gap-3">
            {/* Vignette Total */}
            <div className="bg-card border border-border rounded-xl p-4 min-w-[160px] flex-1">
              <div className="flex items-center gap-2 mb-1">
                <Euro className="w-4 h-4 text-orange-500" />
                <span className="text-xs text-muted-foreground uppercase tracking-wide font-medium">Total OPEX {annee}</span>
              </div>
              <p className="text-2xl font-bold text-orange-600 tabular-nums" style={{ fontFamily: 'Sora, sans-serif' }}>
                {formatEuros(totalGlobal)}
              </p>
              <p className="text-xs text-muted-foreground mt-0.5">{isValidated ? 'Validé' : 'Prévisionnel'}</p>
            </div>
            {/* Vignettes par catégorie */}
            {categoriesPresentes.map(cat => {
              const montant = totalParCategorie[cat] || 0;
              const pct = totalGlobal > 0 ? (montant / totalGlobal * 100).toFixed(1) : '0';
              const colors = getCatColors(cat);
              // Tendance depuis les données sources de l'année courante
              const srcData = getOpexDataForAnnee(annee);
              const tendanceInfo: TendanceCategorie | undefined = srcData.tendances_categories?.[cat];
              const tendance = tendanceInfo?.tendance;
              const variationPct = tendanceInfo?.variation_pct;
              const montantN1 = tendanceInfo?.montant_n1;
              return (
                <div key={cat} className="bg-card border border-border rounded-xl p-4 min-w-[140px] flex-1">
                  <div className="flex items-center justify-between mb-1">
                    <div className="flex items-center gap-2">
                      <BarChart3 className={`w-4 h-4 ${colors.icon}`} />
                      <span className="text-xs text-muted-foreground uppercase tracking-wide font-medium truncate">{cat}</span>
                    </div>
                    {/* Flèche de tendance */}
                    {tendance && tendance !== 'stable' && tendance !== 'new' && variationPct !== null && variationPct !== undefined && (
                      <div className={`flex items-center gap-0.5 text-xs font-semibold px-1.5 py-0.5 rounded-full ${
                        tendance === 'hausse' ? 'bg-red-100 text-red-600' : 'bg-green-100 text-green-600'
                      }`}>
                        {tendance === 'hausse'
                          ? <TrendingUp className="w-3 h-3" />
                          : <TrendingDown className="w-3 h-3" />}
                        <span>{variationPct > 0 ? '+' : ''}{variationPct}%</span>
                      </div>
                    )}
                    {tendance === 'stable' && variationPct !== null && (
                      <div className="flex items-center gap-0.5 text-xs font-semibold px-1.5 py-0.5 rounded-full bg-orange-100 text-orange-600">
                        <span>≈ stable</span>
                      </div>
                    )}
                    {tendance === 'new' && (
                      <div className="flex items-center gap-0.5 text-xs font-semibold px-1.5 py-0.5 rounded-full bg-blue-100 text-blue-600">
                        <span>Nouveau</span>
                      </div>
                    )}
                  </div>
                  <p className={`text-xl font-bold tabular-nums ${colors.kpi}`} style={{ fontFamily: 'Sora, sans-serif' }}>
                    {formatEuros(montant)}
                  </p>
                  <div className="flex items-center justify-between mt-0.5">
                    <p className="text-xs text-muted-foreground">{pct}% du total</p>
                    {montantN1 !== null && montantN1 !== undefined && montantN1 > 0 && (
                      <p className="text-xs text-muted-foreground">N-1 : {formatEuros(montantN1)}</p>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        {/* Barre d'outils */}
        <div className="px-6 py-3 border-b border-border bg-background flex-shrink-0 flex flex-wrap items-center gap-3">
          <div className="relative flex-1 min-w-48 max-w-72">
            <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
            <input
              type="text"
              placeholder={viewMode === 'postes' ? 'Rechercher un poste...' : 'Rechercher un établissement...'}
              value={search}
              onChange={e => setSearch(e.target.value)}
              className="w-full pl-9 pr-3 py-2 text-sm border border-border rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/30"
            />
          </div>

          {viewMode === 'postes' && (
            <div className="flex items-center gap-1.5 flex-wrap">
              <Filter className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
              {CATEGORIES_FILTER.map(cat => (
                <button
                  key={cat}
                  onClick={() => setSelectedCategorie(cat)}
                  className={`px-2.5 py-1 rounded-full text-xs font-medium transition-all ${
                    selectedCategorie === cat
                      ? 'bg-primary text-white'
                      : 'bg-muted text-muted-foreground hover:bg-muted/80'
                  }`}
                >
                  {cat}
                </button>
              ))}
            </div>
          )}

          <div className="ml-auto flex items-center gap-2">
            {viewMode === 'postes' && !isValidated && (
              <button
                onClick={() => { setDialogMode('add'); setEditingLigne(null); }}
                className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-all shadow-sm"
              >
                <Plus className="w-3.5 h-3.5" />
                Ajouter une ligne
              </button>
            )}
            <div className="flex items-center gap-1 bg-muted rounded-lg p-1">
              <button
                onClick={() => { setViewMode('postes'); setSortCol(null); }}
                className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all flex items-center gap-1.5 ${
                  viewMode === 'postes' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
                }`}
              >
                <BarChart3 className="w-3.5 h-3.5" />
                Par poste
              </button>
              <button
                onClick={() => { setViewMode('etablissements'); setSortCol(null); }}
                className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all flex items-center gap-1.5 ${
                  viewMode === 'etablissements' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
                }`}
              >
                <Building2 className="w-3.5 h-3.5" />
                Par établissement
              </button>
            </div>
            <button
              onClick={() => setShowDetails(d => !d)}
              className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
            >
              {showDetails ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
              {showDetails ? 'Masquer détails' : 'Voir détails'}
            </button>
          </div>
        </div>

        {/* Contenu principal */}
        <div className="flex-1 overflow-auto px-6 py-4">

          {/* === VUE PAR POSTE === */}
          {viewMode === 'postes' && (
            <div className="space-y-2">
              {selectedCategorie !== 'Toutes' && (
                <div className="flex items-center justify-between mb-3 px-1">
                  <span className="text-sm text-muted-foreground">
                    {sortedLignes.length} poste{sortedLignes.length > 1 ? 's' : ''} — catégorie <strong>{selectedCategorie}</strong>
                  </span>
                  <span className="text-sm font-semibold text-foreground">
                    Sous-total : <span className="text-primary">{formatEuros(totalFiltre)}</span>
                  </span>
                </div>
              )}

              {!isValidated && (
                <div className="flex items-center gap-2 mb-3 px-1 py-2 rounded-lg bg-amber-50 border border-amber-200 text-amber-700 text-xs">
                  <PencilLine className="w-3.5 h-3.5 flex-shrink-0" />
                  <span>Les montants sont modifiables. Utilisez les boutons <strong>Modifier</strong> / <strong>Supprimer</strong> pour gérer les lignes. La validation définitive verrouille tout.</span>
                </div>
              )}

              <div className="bg-card border border-border rounded-xl overflow-hidden">
                <table className="w-full text-sm">
                  <thead>
                    <tr className="border-b border-border bg-muted/40">
                      <th className="text-left px-4 py-3 font-medium text-muted-foreground w-8">#</th>
                      <th className="text-left px-4 py-3 font-medium text-muted-foreground">
                        <SortBtn col="libelle" label="Poste de charge" />
                      </th>
                      <th className="text-left px-4 py-3 font-medium text-muted-foreground hidden md:table-cell">Catégorie</th>
                      <th className="text-left px-4 py-3 font-medium text-muted-foreground hidden lg:table-cell">Type</th>
                      <th className="text-left px-4 py-3 font-medium text-muted-foreground hidden xl:table-cell">Facturation</th>
                      {showDetails && <th className="text-left px-4 py-3 font-medium text-muted-foreground hidden xl:table-cell">Compte</th>}
                      <th className="text-right px-4 py-3 font-medium text-muted-foreground">Budget N-1</th>
                      <th className="text-right px-4 py-3 font-medium text-muted-foreground" style={{ minWidth: '140px' }}>
                        <SortBtn col="montant" label={`Prév. ${annee}`} />
                      </th>
                      <th className="text-right px-4 py-3 font-medium text-muted-foreground w-24">Répartition</th>
                      {!isValidated && <th className="px-4 py-3 w-20"></th>}
                    </tr>
                  </thead>
                  <tbody>
                    {sortedLignes.map((ligne, idx) => {
                      const pct = totalGlobal > 0 ? (ligne.montant / totalGlobal) * 100 : 0;
                      const isExpanded = expandedPoste === ligne.id;
                      const catColors = getCatColors(ligne.categorie);

                      return (
                        <>
                          <tr
                            key={ligne.id}
                            className={`border-b border-border/50 hover:bg-muted/30 transition-colors ${isExpanded ? 'bg-muted/20' : ''} ${ligne.isCustom ? 'bg-blue-50/30' : ''}`}
                          >
                            <td className="px-4 py-3 text-muted-foreground text-xs">{idx + 1}</td>
                            <td
                              className="px-4 py-3 cursor-pointer"
                              onClick={() => setExpandedPoste(isExpanded ? null : ligne.id)}
                            >
                              <div className="flex items-center gap-2">
                                {isExpanded ? <ChevronUp className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" /> : <ChevronDown className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />}
                                <div>
                                  <p className="font-medium text-foreground leading-tight">{ligne.libelle}</p>
                                  {ligne.fournisseur && ligne.fournisseur !== ligne.libelle && (
                                    <p className="text-xs text-muted-foreground mt-0.5">{ligne.fournisseur}</p>
                                  )}
                                  {ligne.isCustom && (
                                    <span className="text-xs text-blue-500 font-medium">Ajouté manuellement</span>
                                  )}
                                </div>
                              </div>
                            </td>
                            <td className="px-4 py-3 hidden md:table-cell">
                              <span className={`inline-flex px-2 py-0.5 rounded-full text-xs font-medium ${catColors.badge}`}>
                                {ligne.categorie}
                              </span>
                            </td>
                            <td className="px-4 py-3 text-sm text-muted-foreground hidden lg:table-cell">{ligne.type || '—'}</td>
                            <td className="px-4 py-3 text-sm text-muted-foreground hidden xl:table-cell">{ligne.facturation || '—'}</td>
                            {showDetails && <td className="px-4 py-3 text-xs font-mono text-muted-foreground hidden xl:table-cell">{ligne.compte || '—'}</td>}
                            <td className="px-4 py-3 text-right text-sm text-muted-foreground tabular-nums">
                              {formatNum(ligne.budget_n1)}
                            </td>
                            <td className="px-4 py-2 text-right" style={{ minWidth: '140px' }}>
                              {isValidated ? (
                                <span className={`font-semibold tabular-nums ${ligne.montant > 0 ? 'text-foreground' : 'text-muted-foreground'}`}>
                                  {formatNum(ligne.montant)}
                                </span>
                              ) : (
                                <MoneyInput
                                  value={ligne.montant}
                                  onChange={v => handleMontantChange(ligne.id, v)}
                                  disabled={isValidated}
                                />
                              )}
                            </td>
                            <td className="px-4 py-3">
                              <div className="flex items-center gap-2 justify-end">
                                <div className="w-16 bg-muted rounded-full h-1.5 overflow-hidden">
                                  <div
                                    className={`h-1.5 rounded-full transition-all ${catColors.bar}`}
                                    style={{ width: `${Math.min(100, pct * 5)}%` }}
                                  />
                                </div>
                                <span className="text-xs text-muted-foreground tabular-nums w-10 text-right">
                                  {pct.toFixed(1)}%
                                </span>
                              </div>
                            </td>
                            {!isValidated && (
                              <td className="px-2 py-2">
                                <div className="flex items-center gap-1 justify-end">
                                  <button
                                    onClick={() => { setEditingLigne(ligne); setDialogMode('edit'); }}
                                    className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors"
                                    title="Modifier"
                                  >
                                    <Pencil className="w-3.5 h-3.5" />
                                  </button>
                                  <button
                                    onClick={() => setDeleteConfirmId(ligne.id)}
                                    className="p-1.5 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors"
                                    title="Supprimer"
                                  >
                                    <Trash2 className="w-3.5 h-3.5" />
                                  </button>
                                </div>
                              </td>
                            )}
                          </tr>
                          {isExpanded && ligne.detail && (
                            <tr key={`${ligne.id}-detail`} className="bg-blue-50/50 border-b border-border/50">
                              <td />
                              <td colSpan={!isValidated ? 9 : 8} className="px-8 py-3">
                                <div className="flex items-start gap-2 text-sm text-blue-700">
                                  <Info className="w-4 h-4 mt-0.5 flex-shrink-0" />
                                  <div>
                                    {ligne.detail && (<><p className="font-medium">Détail</p><p className="text-blue-600 mt-0.5">{ligne.detail}</p></>)}
                                  </div>
                                </div>
                              </td>
                            </tr>
                          )}
                        </>
                      );
                    })}
                  </tbody>
                  <tfoot>
                    <tr className="bg-muted/40 border-t-2 border-border">
                      <td colSpan={showDetails ? (isValidated ? 7 : 8) : (isValidated ? 6 : 7)} className="px-4 py-3 font-bold text-foreground">
                        TOTAL {selectedCategorie !== 'Toutes' ? selectedCategorie : 'OPEX DSI'} {annee}
                      </td>
                      <td className="px-4 py-3 text-right font-bold text-orange-600 text-base tabular-nums">
                        {formatEuros(totalFiltre)}
                      </td>
                      <td className="px-4 py-3 text-right text-sm text-muted-foreground">
                        {selectedCategorie !== 'Toutes'
                          ? `${totalGlobal > 0 ? ((totalFiltre / totalGlobal) * 100).toFixed(1) : '0'}%`
                          : '100%'}
                      </td>
                      {!isValidated && <td />}
                    </tr>
                  </tfoot>
                </table>
              </div>

              {/* Répartition par catégorie */}
              <div className="mt-6 bg-card border border-border rounded-xl p-5">
                <h3 className="font-semibold text-foreground mb-4 flex items-center gap-2" style={{ fontFamily: 'Sora, sans-serif' }}>
                  <BarChart3 className="w-4 h-4 text-muted-foreground" />
                  Répartition par catégorie — {annee}
                </h3>
                <div className="space-y-3">
                  {Object.entries(totalParCategorie)
                    .filter(([, v]) => v > 0)
                    .sort(([, a], [, b]) => b - a)
                    .map(([cat, montant]) => {
                      const pct = totalGlobal > 0 ? (montant / totalGlobal) * 100 : 0;
                      const colors = getCatColors(cat);
                      return (
                        <div key={cat} className="flex items-center gap-3">
                          <span className={`inline-flex px-2 py-0.5 rounded-full text-xs font-medium w-28 justify-center ${colors.badge}`}>
                            {cat}
                          </span>
                          <div className="flex-1 bg-muted rounded-full h-2 overflow-hidden">
                            <div className={`h-2 rounded-full transition-all ${colors.bar}`} style={{ width: `${pct}%` }} />
                          </div>
                          <span className="text-sm font-semibold text-foreground tabular-nums w-28 text-right">
                            {formatEuros(montant)}
                          </span>
                          <span className="text-xs text-muted-foreground tabular-nums w-12 text-right">
                            {pct.toFixed(1)}%
                          </span>
                        </div>
                      );
                    })}
                </div>
              </div>
            </div>
          )}

          {/* === VUE PAR ÉTABLISSEMENT === */}
          {viewMode === 'etablissements' && (
            <div className="bg-card border border-border rounded-xl overflow-hidden">
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b border-border bg-muted/40">
                    <th className="text-left px-4 py-3 font-medium text-muted-foreground">Code</th>
                    <th className="text-left px-4 py-3 font-medium text-muted-foreground">
                      <SortBtn col="nom" label="Établissement" />
                    </th>
                    <th className="text-right px-4 py-3 font-medium text-muted-foreground hidden lg:table-cell">Base répartition</th>
                    {opexData2026.postes.slice(0, showDetails ? 8 : 4).map(p => (
                      <th key={p.libelle} className="text-right px-3 py-3 font-medium text-muted-foreground text-xs max-w-24 hidden xl:table-cell">
                        <span className="block truncate max-w-20" title={p.libelle}>{p.libelle.split(' ').slice(0, 3).join(' ')}</span>
                      </th>
                    ))}
                    <th className="text-right px-4 py-3 font-medium text-muted-foreground">
                      <SortBtn col="total" label={`Total ${annee}`} />
                    </th>
                  </tr>
                </thead>
                <tbody>
                  {sortedEtabs.map((etab, idx) => (
                    <tr key={etab.code} className={`border-b border-border/50 hover:bg-muted/30 transition-colors ${idx % 2 === 0 ? '' : 'bg-muted/10'}`}>
                      <td className="px-4 py-2.5">
                        <span className="font-mono text-xs bg-muted px-1.5 py-0.5 rounded text-muted-foreground">{etab.code}</span>
                      </td>
                      <td className="px-4 py-2.5 font-medium text-foreground">{etab.nom}</td>
                      <td className="px-4 py-2.5 text-right text-xs text-muted-foreground tabular-nums hidden lg:table-cell">
                        {etab.base_repartition > 0
                          ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(etab.base_repartition) + ' €'
                          : '—'}
                      </td>
                      {opexData2026.postes.slice(0, showDetails ? 8 : 4).map(p => (
                        <td key={p.libelle} className="px-3 py-2.5 text-right text-xs tabular-nums text-muted-foreground hidden xl:table-cell">
                          {etab.montants[p.libelle] > 0
                            ? new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(Math.round(etab.montants[p.libelle])) + ' €'
                            : '—'}
                        </td>
                      ))}
                      <td className="px-4 py-2.5 text-right font-semibold tabular-nums">
                        <span className={etab.total > 0 ? 'text-orange-600' : 'text-muted-foreground'}>
                          {etab.total > 0 ? formatEuros(etab.total) : '—'}
                        </span>
                      </td>
                    </tr>
                  ))}
                </tbody>
                <tfoot>
                  <tr className="bg-muted/40 border-t-2 border-border">
                    <td colSpan={3 + (showDetails ? 8 : 4)} className="px-4 py-3 font-bold text-foreground hidden lg:table-cell">
                      TOTAL — {sortedEtabs.length} établissements
                    </td>
                    <td colSpan={3} className="px-4 py-3 font-bold text-foreground lg:hidden">TOTAL</td>
                    <td className="px-4 py-3 text-right font-bold text-orange-600 text-base tabular-nums">
                      {formatEuros(sortedEtabs.reduce((s, e) => s + e.total, 0))}
                    </td>
                  </tr>
                </tfoot>
              </table>
            </div>
          )}
        </div>
      </main>

      {/* Modale ajout / édition */}
      <Dialog open={dialogMode !== null} onOpenChange={open => { if (!open) { setDialogMode(null); setEditingLigne(null); } }}>
        <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
          <DialogHeader>
            <DialogTitle className="flex items-center gap-2">
              {dialogMode === 'add' ? <Plus className="w-4 h-4" /> : <Pencil className="w-4 h-4" />}
              {dialogMode === 'add' ? 'Ajouter une ligne OPEX' : 'Modifier la ligne'}
            </DialogTitle>
          </DialogHeader>
          {dialogMode === 'add' && (
            <LigneForm
              initial={EMPTY_LIGNE}
              onSave={handleAddLigne}
              onClose={() => setDialogMode(null)}
            />
          )}
          {dialogMode === 'edit' && editingLigne && (
            <LigneForm
              initial={{
                libelle: editingLigne.libelle,
                fournisseur: editingLigne.fournisseur,
                categorie: editingLigne.categorie,
                type: editingLigne.type,
                facturation: editingLigne.facturation,
                compte: editingLigne.compte,
                detail: editingLigne.detail,
                budget_n1: editingLigne.budget_n1,
                montant: editingLigne.montant,
              }}
              onSave={handleEditLigne}
              onClose={() => { setDialogMode(null); setEditingLigne(null); }}
            />
          )}
        </DialogContent>
      </Dialog>

      {/* Confirmation suppression */}
      <AlertDialog open={deleteConfirmId !== null} onOpenChange={open => { if (!open) setDeleteConfirmId(null); }}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Supprimer cette ligne ?</AlertDialogTitle>
            <AlertDialogDescription>
              Cette action est irréversible. La ligne sera définitivement retirée du prévisionnel OPEX {annee}.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Annuler</AlertDialogCancel>
            <AlertDialogAction
              onClick={() => deleteConfirmId && handleDeleteLigne(deleteConfirmId)}
              className="bg-destructive hover:bg-destructive/90 text-white"
            >
              Supprimer
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  );
}
