/* ──────────────────────────────────────────────────────────────────────────
   Fenêtres d'emails du cycle de vente (LIAModal, NotaireEmailModal,
   CompromisEmailModal, AppelFondsModal) déplacées dans
   public/components/VenteEmailModals.jsx (dette D1, 2026-06-24).
   ArchitecteEmailModal supprimée (morte : remplacée par l'étape 2 de
   ConvertirAppelFondsModal dans l'onglet Appels de fonds).
   ────────────────────────────────────────────────────────────────────────── */
/* ── FactureReglerModal ── */
// Modal pour intégrer une facture confirmée dans le Plan Hebdo et la marquer Réglée.
function FactureReglerModal({notifId, factureData, proposedAction, projects, rows, fiche, allValues, onSave, logMouvementAuto, onClose}) {
  // facture_data est stocké sous forme de tableau (1 entrée par facture) par
  // lib/invoiceExtract.js. Selon le point d'appel, on reçoit ici soit le tableau
  // brut (bouton « Régler » facture unique), soit déjà un objet (cas multi-facture
  // qui passe facture_data:facture). On normalise pour toujours travailler sur un
  // objet facture, sinon les lectures factureData?.montant_ttc tombent sur le
  // tableau et renvoient undefined → modale vide.
  const fdObj = Array.isArray(factureData) ? (factureData[0] || null) : (factureData || null);
  // Initialisation depuis proposed_action si disponible, sinon valeurs vides
  const pa = proposedAction?.type==="create_flux" ? proposedAction.params : null;
  const [projId,     setProjId]     = useState(pa?.programmeId||"");
  const [rowId,      setRowId]      = useState(pa?.rowId||"");
  const [weekIso,    setWeekIso]    = useState(todayIso);
  const [montant,    setMontant]    = useState(
    fdObj?.montant_ttc ? String(-Math.abs(fdObj.montant_ttc))
    : pa?.montant ? String(-Math.abs(pa.montant)) : ""
  );
  const [redistWeek, setRedistWeek] = useState("");
  const [saving,     setSaving]     = useState(false);

  const selStyle = {width:"100%",padding:"6px 8px",border:"1px solid #e2e8f0",borderRadius:6,fontSize:13,background:"#f8fafc"};
  const projRows = rows.filter(r=>r.projetId===projId);

  // Calcul des flux postérieurs à la semaine choisie sur la ligne sélectionnée
  const parsedMont = parseFloat(String(montant).replace(",",".").replace(/\s/g,""));
  const montVal = isNaN(parsedMont) ? null : parsedMont;
  const futureVals = useMemo(()=>{
    if(!rowId||!weekIso) return [];
    return (allValues||[])
      .filter(v=>v.rowId===rowId && v.weekIso>weekIso)
      .sort((a,b)=>a.weekIso.localeCompare(b.weekIso));
  },[allValues, rowId, weekIso]);
  const showRedist = montVal!==null && montVal!==0 && futureVals.length>0;

  // Montant déjà présent dans la cellule ciblée (ligne + semaine). Le règlement
  // s'AJOUTE à ce montant (handleCellSave additive) → on l'affiche pour éviter la
  // surprise de voir un total différent du montant de la facture.
  const existingCell = useMemo(()=>
    (allValues||[]).find(v=>v.rowId===rowId && v.weekIso===weekIso) || null
  ,[allValues, rowId, weekIso]);
  const existingMont = existingCell ? (Number(existingCell.montant)||0) : 0;

  // Réinitialise la redistribution si la semaine ou la ligne change
  useEffect(()=>{ setRedistWeek(""); },[rowId, weekIso]);

  // Suggestion de ligne depuis le fournisseur (se déclenche au montage et si projId change)
  // Ne s'exécute pas si la rowId est déjà pré-remplie depuis proposed_action
  useEffect(()=>{
    if(!fdObj?.fournisseur || !projId || rowId) return;
    const fournisseurLow = fdObj.fournisseur.toLowerCase();
    const ficheData = fiche && fiche[projId];
    const intervenants = ficheData?.intervenants||[];
    const match = intervenants.find(iv=>
      (iv.entrepriseNom||"").toLowerCase().includes(fournisseurLow) ||
      fournisseurLow.includes((iv.entrepriseNom||"").toLowerCase())
    );
    if(match && match.typeCout){
      const matchRow = projRows.find(r=>r.label.toLowerCase().includes(match.typeCout.toLowerCase()));
      if(matchRow) setRowId(matchRow.id);
    }
  },[projId]);

  const handleSave = async () => {
    if(!rowId||!weekIso||!montant){ alert("Veuillez remplir tous les champs obligatoires."); return; }
    const mont = parseFloat(montant);
    if(isNaN(mont)){ alert("Montant invalide."); return; }
    setSaving(true);
    const redist = redistWeek ? {weekIso:redistWeek, adjustment:-mont} : null;
    await onSave(rowId, weekIso, mont, redist);
    // Log auto au Journal des mouvements — règle stricte « R seulement » : uniquement
    // si la ligne est en statut R ET la semaine ≤ aujourd'hui (vrai décaissement).
    try {
      const row = (rows||[]).find(r=>r.id===rowId);
      const today = new Date().toISOString().slice(0,10);
      if(logMouvementAuto && row && row.statut==="R" && weekIso<=today){
        logMouvementAuto({
          source:"facture",
          programmeId: projId, rowId, weekIso,
          montant: (Number(existingMont)||0) + mont,         // nouvelle valeur de la cellule (règlement additif)
          montantPrecedent: (existingMont?existingMont:""),
          destinataireNom: (fdObj && fdObj.fournisseur) || "",
          description: (fdObj && fdObj.description) || ("Facture"+(fdObj&&fdObj.fournisseur?" "+fdObj.fournisseur:"")),
          numeroFacture: (fdObj && fdObj.numero_facture) || "",
        });
      }
    } catch(e){ console.warn("[journal] log facture échoué:", e.message); }
    // Passe la facture en "réglée"
    await fetch("/api/agent/notifications/"+notifId+"/facture",{
      method:"PATCH", headers:{"Content-Type":"application/json"},
      body:JSON.stringify({facture_status:"réglée"})
    }).catch(()=>{});
    setSaving(false);
    onClose();
  };

  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.6)",zIndex:3000,display:"flex",alignItems:"center",justifyContent:"center"}}
      onClick={e=>{if(e.target===e.currentTarget)onClose();}}>
      <div style={{background:"#fff",borderRadius:14,padding:24,maxWidth:520,width:"92%",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0004"}}>
        <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
          <div style={{fontWeight:800,fontSize:16}}>💶 Régler la facture & intégrer au Plan Hebdo</div>
          <button onClick={onClose} style={{background:"none",border:"none",fontSize:20,cursor:"pointer",color:"#64748b"}}>✕</button>
        </div>

        {/* Rappel des données de la facture */}
        <div style={{background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:10,padding:12,marginBottom:16,display:"grid",gridTemplateColumns:"auto 1fr",gap:"3px 12px",fontSize:13}}>
          {fdObj?.fournisseur&&<><span style={{color:"#64748b",fontWeight:600}}>Fournisseur</span><span style={{fontWeight:700}}>{fdObj.fournisseur}</span></>}
          {fdObj?.numero_facture&&<><span style={{color:"#64748b",fontWeight:600}}>N° facture</span><span>{fdObj.numero_facture}</span></>}
          {fdObj?.date_facture&&<><span style={{color:"#64748b",fontWeight:600}}>Date</span><span>{new Date(fdObj.date_facture+"T12:00:00Z").toLocaleDateString("fr-FR")}</span></>}
          {fdObj?.montant_ttc!=null&&<><span style={{color:"#64748b",fontWeight:600}}>Montant TTC</span><span style={{fontWeight:700,color:"#1d4ed8"}}>{fmtEur(fdObj.montant_ttc)}</span></>}
        </div>

        <div style={{fontWeight:700,fontSize:13,color:"#334155",marginBottom:10}}>📋 Ajouter au Plan Hebdo</div>
        <div style={{display:"flex",flexDirection:"column",gap:10}}>
          <div>
            <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Programme *</label>
            <select value={projId} onChange={e=>{setProjId(e.target.value);setRowId("");}} style={selStyle}>
              <option value="">— Sélectionner un programme —</option>
              <GroupedProgrammeOptions projects={projects} fiches={fiche}/>
            </select>
          </div>
          <div>
            <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Ligne Plan Hebdo *</label>
            <select value={rowId} onChange={e=>setRowId(e.target.value)} style={selStyle} disabled={!projId}>
              <option value="">— Sélectionner une ligne —</option>
              {projRows.map(r=><option key={r.id} value={r.id}>{r.label}</option>)}
            </select>
          </div>
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10}}>
            <div>
              <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Semaine *</label>
              <select value={weekIso} onChange={e=>setWeekIso(e.target.value)} style={selStyle}>
                {weekDates.map(w=><option key={w} value={w}>{fmtW(w)}</option>)}
              </select>
            </div>
            <div>
              <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Montant € (négatif = dépense) *</label>
              <input type="number" step="0.01" value={montant} onChange={e=>setMontant(e.target.value)}
                style={selStyle} placeholder="-12000"/>
            </div>
          </div>
        </div>

        {/* Cumul : la cellule contient déjà un montant sur cette semaine */}
        {existingMont!==0 && montVal!==null && montVal!==0 && (
          <div style={{background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,padding:"8px 12px",marginTop:12,fontSize:12,color:"#1d4ed8"}}>
            ⚠️ Cette ligne contient déjà <b>{fmtEur(existingMont)}</b> sur cette semaine. Ce règlement sera <b>ajouté</b> → nouveau total <b>{fmtEur(existingMont+montVal)}</b>.
          </div>
        )}

        {/* Redistribution des flux postérieurs */}
        {showRedist&&(
          <div style={{background:"#fffbeb",borderRadius:8,border:"1px solid #fde68a",padding:12,marginTop:12}}>
            <div style={{fontSize:12,fontWeight:700,color:"#b45309",marginBottom:4}}>
              ⚖️ Flux ajouté : {montVal>0?"+":""}{fmtEur(montVal,true)}
            </div>
            <div style={{fontSize:11,color:"#92400e",marginBottom:8}}>
              Pour maintenir le total de la ligne, souhaitez-vous reporter {fmtEur(Math.abs(montVal),true)} sur un flux futur ?
            </div>
            <select value={redistWeek} onChange={e=>setRedistWeek(e.target.value)}
              style={{background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,color:"#92400e",padding:"5px 8px",fontSize:12,width:"100%"}}>
              <option value="">— Ne pas redistribuer —</option>
              {futureVals.map(v=>(
                <option key={v.weekIso} value={v.weekIso}>
                  {fmtW(v.weekIso)} : {fmtEur(v.montant)} → {fmtEur(v.montant+(-montVal))}
                </option>
              ))}
            </select>
          </div>
        )}

        <div style={{display:"flex",gap:8,marginTop:18,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{padding:"7px 16px",borderRadius:6,border:"1px solid #e2e8f0",background:"#f8fafc",cursor:"pointer",fontSize:13}}>Annuler</button>
          <button onClick={handleSave} disabled={saving||!rowId||!weekIso||!montant}
            style={{background:saving||!rowId||!weekIso||!montant?"#94a3b8":"#16a34a",color:"#fff",border:"none",borderRadius:7,padding:"8px 18px",cursor:"pointer",fontSize:13,fontWeight:600}}>
            {saving?"Enregistrement…":"✓ Marquer Réglée & Ajouter au Plan"}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── AppelReplanModal ── */
// Modale de replanification des flux à venir de la ligne « Lot N » du plan de
// trésorerie, déclenchée à l'étape 5 du workflow appel de fonds (envoi mail
// client). Garantit l'invariant : sum(cellules ligne) = prixReel.
// L'appel atomique au backend (POST /api/appels-eg/:id/replan) met à jour les
// cellules ET pose appel_client_sent=TRUE en une transaction.
function AppelReplanModal({ appel, prixReel, currentRowValues, lotN, programmeNom, attestNotifId, onConfirm, onCancel }) {
  // Cellules existantes triées, séparées passé / futur (passé = non modifiable ici)
  const initialFuture = useMemo(() => {
    return (currentRowValues || [])
      .filter(v => v.weekIso >= todayIso)
      .sort((a, b) => a.weekIso.localeCompare(b.weekIso))
      .map(v => ({ key: v.weekIso, weekIso: v.weekIso, montant: Number(v.montant) }));
  }, [currentRowValues]);

  const pastCellsSum = useMemo(() => (
    (currentRowValues || [])
      .filter(v => v.weekIso < todayIso)
      .reduce((s, v) => s + Number(v.montant), 0)
  ), [currentRowValues]);

  const [editRows, setEditRows] = useState(initialFuture);
  const [saving,   setSaving]   = useState(false);
  const [error,    setError]    = useState("");

  const futureWeekOptions = useMemo(() => weekDates.filter(w => w >= todayIso), []);

  // Détection des doublons de semaine
  const dupWeeks = useMemo(() => {
    const seen = new Set(), dups = new Set();
    for (const r of editRows) {
      if (r.weekIso && seen.has(r.weekIso)) dups.add(r.weekIso);
      seen.add(r.weekIso);
    }
    return dups;
  }, [editRows]);

  const totalFuture = editRows.reduce((s, r) => s + (Number(r.montant) || 0), 0);
  const total       = pastCellsSum + totalFuture;
  const ecart       = total - prixReel;
  const ecartOk     = Math.abs(ecart) <= 1;
  const allRowsValid = editRows.every(r => r.weekIso && Number(r.montant) >= 0);
  const canValidate = ecartOk && allRowsValid && dupWeeks.size === 0 && !saving;

  const updateRow = (i, field, value) => {
    setEditRows(rs => rs.map((r, idx) => idx === i ? { ...r, [field]: value } : r));
  };
  const addRow = () => {
    setEditRows(rs => [...rs, { key: `new-${Date.now()}-${Math.random()}`, weekIso: '', montant: 0 }]);
  };
  const removeRow = (i) => {
    setEditRows(rs => rs.filter((_, idx) => idx !== i));
  };

  const handleConfirm = async () => {
    setError("");
    // Construit le diff vs état initial
    const newMap = new Map();
    for (const r of editRows) {
      if (!r.weekIso) continue;
      const m = Number(r.montant) || 0;
      newMap.set(r.weekIso, (newMap.get(r.weekIso) || 0) + m);
    }
    const oldMap = new Map(initialFuture.map(r => [r.weekIso, r.montant]));

    const changes = [];
    for (const [weekIso, newM] of newMap) {
      const oldM = oldMap.get(weekIso) ?? 0;
      if (Math.abs(newM - oldM) > 0.005) changes.push({ weekIso, montant: Math.round(newM * 100) / 100 });
    }
    for (const [weekIso, _] of oldMap) {
      if (!newMap.has(weekIso)) changes.push({ weekIso, montant: 0 });
    }
    // Cas dégénéré : aucun changement de plan, mais on doit quand même marquer
    // l'appel comme envoyé côté backend. On envoie un no-op (cellule cible existante).
    if (changes.length === 0) {
      const first = Array.from(newMap)[0];
      if (first) changes.push({ weekIso: first[0], montant: Math.round(first[1] * 100) / 100 });
    }

    setSaving(true);
    try {
      await onConfirm(changes, attestNotifId);
    } catch (e) {
      setError(e?.message || "Erreur lors de la sauvegarde");
      setSaving(false);
    }
  };

  const inp     = { padding: "5px 7px", border: "1px solid #cbd5e1", borderRadius: 6, fontSize: 13, background: "#fff" };
  const numInp  = { ...inp, textAlign: "right", width: "100%" };
  const selStyle = { ...inp, width: "100%" };

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.55)", zIndex: 3100, display: "flex", alignItems: "center", justifyContent: "center", padding: 16 }}
      onClick={e => { if (e.target === e.currentTarget) onCancel(); }}>
      <div style={{ background: "#fff", borderRadius: 14, padding: 24, width: 640, maxWidth: "96vw", maxHeight: "90vh", overflowY: "auto", boxShadow: "0 8px 40px #0004" }}
        onClick={e => e.stopPropagation()}>

        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
          <div style={{ fontWeight: 800, fontSize: 16 }}>📅 Replanifier les flux à venir</div>
          <button onClick={onCancel} style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer", color: "#64748b" }}>✕</button>
        </div>
        <div style={{ fontSize: 12, color: "#64748b", marginBottom: 14 }}>
          {programmeNom} · Lot {lotN}
        </div>

        {/* Bandeau appel + invariant */}
        <div style={{ background: "#fef3c7", border: "1px solid #fde68a", borderRadius: 8, padding: "10px 12px", marginBottom: 12, fontSize: 12, lineHeight: 1.6 }}>
          Vous allez envoyer un appel de fonds de <b>{fmtEur(Number(appel.montant_client) || 0)}</b> ({appel.pct_delta || appel.eg_pct}%) au client.
          <br/>Ajustez si nécessaire les dates et montants des flux à venir de ce lot — la somme totale de la ligne doit rester égale au <b>prix réel ({fmtEur(prixReel)})</b>.
        </div>

        {/* Flux passés (info) */}
        {pastCellsSum > 0 && (
          <div style={{ background: "#f8fafc", border: "1px solid #e2e8f0", borderRadius: 8, padding: "6px 10px", marginBottom: 10, fontSize: 11, color: "#64748b" }}>
            Flux déjà passés sur cette ligne (non modifiables) : <b>{fmtEur(pastCellsSum)}</b>
          </div>
        )}

        {/* Tableau cellules futures éditables */}
        <div style={{ marginBottom: 10 }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 32px", gap: 6, alignItems: "center", marginBottom: 4, fontSize: 11, fontWeight: 700, color: "#64748b" }}>
            <div>Semaine</div>
            <div style={{ textAlign: "right" }}>Montant (€)</div>
            <div></div>
          </div>
          {editRows.length === 0 && (
            <div style={{ fontSize: 12, color: "#94a3b8", fontStyle: "italic", padding: "8px 0" }}>
              Aucune cellule à venir. Cliquez sur « + Ajouter une cellule ».
            </div>
          )}
          {editRows.map((r, i) => {
            const isDup = r.weekIso && dupWeeks.has(r.weekIso);
            return (
              <div key={r.key} style={{ display: "grid", gridTemplateColumns: "1fr 1fr 32px", gap: 6, alignItems: "center", marginBottom: 4 }}>
                <select value={r.weekIso} onChange={e => updateRow(i, "weekIso", e.target.value)}
                  style={{ ...selStyle, border: isDup ? "1px solid #f87171" : selStyle.border, background: isDup ? "#fef2f2" : selStyle.background }}>
                  <option value="">— Choisir —</option>
                  {futureWeekOptions.map(w => <option key={w} value={w}>{fmtW(w)}</option>)}
                </select>
                <input type="number" step="0.01" min="0" value={r.montant} onChange={e => updateRow(i, "montant", e.target.value)}
                  style={numInp} />
                <button onClick={() => removeRow(i)} title="Supprimer cette cellule"
                  style={{ background: "#fee2e2", border: "1px solid #fca5a5", borderRadius: 6, padding: "3px 6px", cursor: "pointer", color: "#dc2626", fontSize: 13 }}>×</button>
              </div>
            );
          })}
          <button onClick={addRow}
            style={{ background: "#f8fafc", border: "1px dashed #cbd5e1", borderRadius: 6, padding: "5px 12px", cursor: "pointer", fontSize: 12, color: "#475569", marginTop: 4 }}>
            + Ajouter une cellule
          </button>
        </div>

        {/* Doublons */}
        {dupWeeks.size > 0 && (
          <div style={{ background: "#fef2f2", border: "1px solid #fca5a5", borderRadius: 8, padding: "6px 10px", marginBottom: 8, fontSize: 11, color: "#dc2626" }}>
            ⚠ Doublons de semaine : {Array.from(dupWeeks).map(fmtW).join(", ")} — fusionnez-les avant de valider.
          </div>
        )}

        {/* Compteur total / écart */}
        <div style={{
          background: ecartOk ? "#f0fdf4" : "#fff7ed",
          border: ecartOk ? "1px solid #86efac" : "2px solid #fed7aa",
          borderRadius: 8, padding: "10px 12px", marginBottom: 14, fontSize: 12, lineHeight: 1.8,
        }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: "0 16px" }}>
            <span>Total ligne après modifications</span><b>{fmtEur(total)}</b>
            <span>Attendu (prix réel)</span><b>{fmtEur(prixReel)}</b>
            <span>Écart</span><b style={{ color: ecartOk ? "#15803d" : "#c2410c" }}>{ecart >= 0 ? "+" : ""}{fmtEur(ecart)}</b>
          </div>
        </div>

        {error && (
          <div style={{ background: "#fee2e2", border: "1px solid #fca5a5", borderRadius: 8, padding: "8px 12px", marginBottom: 10, fontSize: 12, color: "#dc2626" }}>
            ❌ {error}
          </div>
        )}

        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <button onClick={onCancel} disabled={saving}
            style={{ padding: "7px 16px", borderRadius: 6, border: "1px solid #e2e8f0", background: "#f8fafc", cursor: saving ? "default" : "pointer", fontSize: 13 }}>
            Annuler
          </button>
          <button onClick={handleConfirm} disabled={!canValidate}
            style={{
              background: canValidate ? "#0369a1" : "#94a3b8", color: "#fff", border: "none",
              borderRadius: 7, padding: "8px 22px", cursor: canValidate ? "pointer" : "default", fontSize: 13, fontWeight: 700,
            }}>
            {saving ? "⏳ Validation…" : "✓ Valider le plan & marquer envoyé"}
          </button>
        </div>
      </div>
    </div>
  );
}


function NewProgrammeModal({onCreate,onClose}) {
  const [ville,setVille]=useState("");
  const [nom,setNom]=useState("");
  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:14,padding:24,width:360,boxShadow:"0 8px 40px #0003"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:16,marginBottom:16}}>Nouveau programme</div>
        <input placeholder="Ville" value={ville} onChange={e=>setVille(e.target.value)}
          style={{background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"8px 12px",fontSize:13,width:"100%",marginBottom:10}}/>
        <input placeholder="Nom du programme" value={nom} onChange={e=>setNom(e.target.value)}
          style={{background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"8px 12px",fontSize:13,width:"100%",marginBottom:16}}/>
        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 16px",fontSize:13,cursor:"pointer"}}>Annuler</button>
          <button onClick={()=>{if(ville.trim()||nom.trim())onCreate(ville.trim(),nom.trim());}}
            style={{background:"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 20px",fontSize:13,fontWeight:700,cursor:"pointer"}}>Créer</button>
        </div>
      </div>
    </div>
  );
}

function DeleteProgrammeModal({proj,onConfirm,onClose}) {
  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:14,padding:24,width:400,boxShadow:"0 8px 40px #0003"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:16,marginBottom:12}}>Supprimer le programme ?</div>
        <div style={{fontSize:13,color:"#64748b",marginBottom:20}}>
          Cette action supprimera définitivement <b>{proj.ville&&proj.ville+" — "}{proj.nom}</b> et toutes ses données (lots, flux, intervenants). Cette opération est irréversible.
        </div>
        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 16px",fontSize:13,cursor:"pointer"}}>Annuler</button>
          <button onClick={onConfirm} style={{background:"#dc2626",color:"#fff",border:"none",borderRadius:8,padding:"8px 20px",fontSize:13,fontWeight:700,cursor:"pointer"}}>Supprimer</button>
        </div>
      </div>
    </div>
  );
}


/* Calcule la variation de solde par rapport à un snapshot passé.
   Retourne aussi snapshotDate (ISO YYYY-MM-DD) pour requêter l'historique des écritures. */
function calcVariation(soldeFinOp, progHistory, daysAgo) {
  const targetDate = new Date();
  targetDate.setDate(targetDate.getDate() - daysAgo);
  const targetStr = targetDate.toISOString().slice(0, 10);
  // Snapshot le plus récent sur ou avant targetDate (liste déjà triée DESC)
  const snap = progHistory.find(h => h.snapshot_date <= targetStr);
  if (!snap) return null;
  const prev = Number(snap.solde);
  if (prev === 0) return null;
  const diff = soldeFinOp - prev;
  const pct  = (diff / Math.abs(prev)) * 100;
  return { diff, pct, snapshotDate: snap.snapshot_date, snapshotSolde: prev };
}

/* Affiche une puce de variation : ↑ +2.3% (+12 340 €) SEM
   Cliquable si onClick fourni : ouvre la modale détaillée des écritures. */
function VariationPuce({variation, label, onClick}) {
  if (!variation) return null;
  const { diff, pct } = variation;
  const isPos   = diff >= 0;
  const isAlert = Math.abs(pct) > 5;
  const color   = isAlert ? '#dc2626' : isPos ? '#15803d' : '#64748b';
  const arrow   = isPos ? '↑' : '↓';
  const sign    = isPos ? '+' : '';
  const baseStyle = {color, fontSize:10, whiteSpace:'nowrap'};
  if (!onClick) {
    return <span style={baseStyle}>{arrow} {sign}{pct.toFixed(1)}% ({sign}{fmtEur(diff)}) {label}</span>;
  }
  return (
    <button
      onClick={e=>{e.stopPropagation(); onClick();}}
      title="Voir les écritures qui expliquent cette variation"
      style={{...baseStyle, background:'none', border:'none', padding:0, margin:0,
        cursor:'pointer', textDecoration:'underline', textDecorationStyle:'dotted',
        textUnderlineOffset:2, fontFamily:'inherit'}}>
      {arrow} {sign}{pct.toFixed(1)}% ({sign}{fmtEur(diff)}) {label}
    </button>
  );
}

/* Modale détaillant les écritures qui expliquent une variation. */
function VariationDetailsModal({proj, periodLabel, variation, currentSolde, onClose}) {
  const [entries, setEntries] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError]     = useState(null);
  const [expanded, setExpanded] = useState(new Set());

  const SOURCE_META = {
    manual:        { label: 'Manuel',     color: '#2563eb', bg: '#eff6ff' },
    status_change: { label: 'Statut lot', color: '#7c3aed', bg: '#f5f3ff' },
    appel_eg:      { label: 'Appel EG',   color: '#d97706', bg: '#fffbeb' },
    facture:       { label: 'Facture',    color: '#059669', bg: '#f0fdf4' },
  };
  const sourceMeta = s => SOURCE_META[s] || { label: s || '?', color: '#6b7280', bg: '#f9fafb' };

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    const params = new URLSearchParams({ projet_id: proj.id, since: variation.snapshotDate, limit: '500' });
    fetch('/api/plan-changes?' + params.toString())
      .then(r => r.ok ? r.json() : Promise.reject(new Error('Erreur ' + r.status)))
      .then(d => { if (!cancelled) setEntries(d.summary || []); })
      .catch(e => { if (!cancelled) setError(e.message); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [proj.id, variation.snapshotDate]);

  // ── Réconciliation ─────────────────────────────────────────────────────────
  const totalExplique = entries.reduce((s, e) => s + (Number(e.delta_total) || 0), 0);
  const variationAffichee = variation.diff;
  const ecart = variationAffichee - totalExplique;
  const seuilOk = Math.abs(ecart) <= 10;

  // ── Helpers formatage ──────────────────────────────────────────────────────
  function fmtDateTime(iso) {
    if (!iso) return '—';
    const d = new Date(iso);
    return d.toLocaleDateString('fr-FR', { day:'2-digit', month:'2-digit', year:'2-digit' })
      + ' ' + d.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' });
  }
  function fmtSigned(n) {
    if (n == null) return '—';
    const v = Number(n);
    const s = Math.abs(v).toLocaleString('fr-FR', { minimumFractionDigits:0, maximumFractionDigits:0 });
    return (v >= 0 ? '+' : '−') + ' ' + s + ' €';
  }
  function deltaColor(n) {
    if (n == null) return C.muted;
    return Number(n) >= 0 ? '#059669' : '#dc2626';
  }
  function toggleExpand(id) {
    setExpanded(prev => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }

  const th = { padding:'8px 12px', textAlign:'left', fontSize:11, fontWeight:700,
               color:C.muted, borderBottom:'1px solid '+C.border, whiteSpace:'nowrap' };
  const td = { padding:'8px 12px', fontSize:13, borderBottom:'1px solid '+C.border, verticalAlign:'top' };

  return (
    <div onClick={e=>{e.stopPropagation(); onClose();}}
      style={{position:'fixed', inset:0, background:'rgba(15,23,42,0.55)', zIndex:1000,
              display:'flex', alignItems:'flex-start', justifyContent:'center', padding:'40px 16px', overflowY:'auto'}}>
      <div onClick={e=>e.stopPropagation()}
        style={{background:C.card, borderRadius:12, maxWidth:980, width:'100%', boxShadow:'0 20px 50px #0004', overflow:'hidden'}}>

        {/* ── En-tête ── */}
        <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start',
                     padding:'18px 24px', borderBottom:'1px solid '+C.border}}>
          <div>
            <div style={{fontSize:16, fontWeight:800, color:C.text}}>
              Variation {periodLabel} — {proj.ville ? proj.ville+' — ' : ''}{proj.nom}
            </div>
            <div style={{fontSize:12, color:C.muted, marginTop:4}}>
              Depuis le snapshot du {variation.snapshotDate}
            </div>
          </div>
          <button onClick={onClose}
            style={{background:'none', border:'none', cursor:'pointer', fontSize:22, color:C.muted, padding:'0 4px'}}>×</button>
        </div>

        {/* ── Réconciliation ── */}
        <div style={{padding:'14px 24px', background:C.bg, borderBottom:'1px solid '+C.border,
                     display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:16}}>
          <div>
            <div style={{fontSize:10, color:C.muted, textTransform:'uppercase', letterSpacing:0.5, marginBottom:2}}>
              Variation affichée
            </div>
            <div style={{fontSize:16, fontWeight:700, color:deltaColor(variationAffichee)}}>
              {fmtSigned(variationAffichee)}
            </div>
            <div style={{fontSize:11, color:C.muted, marginTop:2}}>
              {fmtEur(variation.snapshotSolde)} → {fmtEur(currentSolde)}
            </div>
          </div>
          <div>
            <div style={{fontSize:10, color:C.muted, textTransform:'uppercase', letterSpacing:0.5, marginBottom:2}}>
              Total expliqué (écritures)
            </div>
            <div style={{fontSize:16, fontWeight:700, color:deltaColor(totalExplique)}}>
              {fmtSigned(totalExplique)}
            </div>
            <div style={{fontSize:11, color:C.muted, marginTop:2}}>
              {entries.length} écriture{entries.length !== 1 ? 's' : ''}
            </div>
          </div>
          <div>
            <div style={{fontSize:10, color:C.muted, textTransform:'uppercase', letterSpacing:0.5, marginBottom:2}}>
              Écart
            </div>
            <div style={{fontSize:16, fontWeight:700, color: seuilOk ? '#059669' : '#d97706'}}>
              {fmtSigned(ecart)}
            </div>
            <div style={{fontSize:11, color: seuilOk ? '#059669' : '#d97706', marginTop:2}}>
              {seuilOk ? '✓ Réconciliation OK (≤ 10 €)' : '⚠ Écart non tracé dans les écritures'}
            </div>
          </div>
        </div>

        {!seuilOk && !loading && !error && (
          <div style={{padding:'10px 24px', background:'#fffbeb', borderBottom:'1px solid '+C.border,
                       fontSize:12, color:'#92400e'}}>
            Une partie de la variation ({fmtSigned(ecart)}) n'est pas couverte par les écritures listées —
            probablement liée à un changement de fiche/lots ou à des snapshots manquants sur la période.
          </div>
        )}

        {/* ── Tableau écritures ── */}
        <div style={{maxHeight:'60vh', overflowY:'auto'}}>
          {loading && <div style={{padding:48, textAlign:'center', color:C.muted}}>Chargement…</div>}
          {error && (
            <div style={{margin:24, padding:'12px 16px', background:'#fef2f2', border:'1px solid #fecaca',
                         borderRadius:8, color:'#dc2626', fontSize:13}}>
              Erreur : {error}
            </div>
          )}
          {!loading && !error && entries.length === 0 && (
            <div style={{padding:48, textAlign:'center', color:C.muted, fontSize:14}}>
              Aucune écriture enregistrée sur cette période.
            </div>
          )}
          {!loading && !error && entries.length > 0 && (
            <table style={{width:'100%', borderCollapse:'collapse'}}>
              <thead>
                <tr style={{background:C.bg, position:'sticky', top:0}}>
                  <th style={th}>Date</th>
                  <th style={th}>Lot</th>
                  <th style={th}>Action</th>
                  <th style={th}>Source</th>
                  <th style={{...th, textAlign:'right'}}>Impact</th>
                  <th style={{...th, textAlign:'center'}}>Cellules</th>
                  <th style={{...th, textAlign:'center'}}>Détail</th>
                </tr>
              </thead>
              <tbody>
                {entries.map(entry => {
                  const sm = sourceMeta(entry.source);
                  const isOpen = expanded.has(entry.id);
                  const hasVerbose = entry.verbose && entry.verbose.length > 0;
                  return (
                    <React.Fragment key={entry.id}>
                      <tr style={{background: isOpen ? C.bg : 'transparent'}}>
                        <td style={{...td, fontSize:12, color:C.muted, whiteSpace:'nowrap'}}>
                          {fmtDateTime(entry.created_at)}
                        </td>
                        <td style={{...td, color:C.muted}}>{entry.lot_numero || '—'}</td>
                        <td style={td}>
                          <div style={{fontWeight:500}}>{entry.action_label}</div>
                          {entry.weeks_concernees && (
                            <div style={{fontSize:11, color:C.muted, marginTop:2}}>{entry.weeks_concernees}</div>
                          )}
                        </td>
                        <td style={td}>
                          <span style={{background:sm.bg, color:sm.color, borderRadius:5,
                                        padding:'2px 8px', fontSize:11, fontWeight:700}}>{sm.label}</span>
                        </td>
                        <td style={{...td, textAlign:'right', fontWeight:700, color:deltaColor(entry.delta_total)}}>
                          {fmtSigned(entry.delta_total)}
                        </td>
                        <td style={{...td, textAlign:'center', color:C.muted, fontSize:12}}>{entry.nb_cellules}</td>
                        <td style={{...td, textAlign:'center'}}>
                          {hasVerbose ? (
                            <button onClick={()=>toggleExpand(entry.id)}
                              style={{background:'none', border:'none', cursor:'pointer',
                                      color:C.accent, fontSize:18, lineHeight:1}}>
                              {isOpen ? '▲' : '▼'}
                            </button>
                          ) : (
                            <span style={{color:C.muted, fontSize:11}} title="Détail expiré (> 7 jours)">—</span>
                          )}
                        </td>
                      </tr>
                      {isOpen && hasVerbose && (
                        <tr>
                          <td colSpan={7} style={{padding:0}}>
                            <div style={{background:C.bg, borderTop:'1px solid '+C.border,
                                         borderBottom:'1px solid '+C.border, padding:'8px 16px 12px'}}>
                              <table style={{width:'100%', borderCollapse:'collapse', fontSize:12}}>
                                <thead>
                                  <tr>
                                    <th style={{...th, fontSize:10, padding:'4px 8px'}}>Ligne</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px'}}>Semaine</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px', textAlign:'right'}}>Avant</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px', textAlign:'right'}}>Après</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px', textAlign:'right'}}>Δ</th>
                                  </tr>
                                </thead>
                                <tbody>
                                  {entry.verbose.map((v, i) => (
                                    <tr key={i}>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, color:C.text}}>{v.rowLabel}</td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, color:C.muted, fontFamily:'monospace'}}>{v.weekIso}</td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, textAlign:'right', color:C.muted}}>
                                        {Number(v.montantAvant).toLocaleString('fr-FR', {minimumFractionDigits:0, maximumFractionDigits:0})} €
                                      </td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, textAlign:'right', fontWeight:600}}>
                                        {Number(v.montantApres).toLocaleString('fr-FR', {minimumFractionDigits:0, maximumFractionDigits:0})} €
                                      </td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, textAlign:'right', fontWeight:700, color:deltaColor(v.delta)}}>
                                        {fmtSigned(v.delta)}
                                      </td>
                                    </tr>
                                  ))}
                                </tbody>
                              </table>
                            </div>
                          </td>
                        </tr>
                      )}
                    </React.Fragment>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}

function ProgrammeCard({proj,fiche,soldeFinOp,history,onSelect,onDelete}) {
  const statut=STATUT_GROUPES.find(s=>s.key===(fiche.statut||"En cours"))||STATUT_GROUPES[0];
  const lots=(fiche.lots||[]);
  const nbLots=lots.length;
  const nbVendus=lots.filter(l=>isLotVenduOuAvance(l.statutCommercial||"")).length;
  // Historique filtré sur ce programme, déjà trié DESC par le serveur
  const progHistory=(history||[]).filter(h=>h.programme_id===proj.id);
  const j1 =calcVariation(soldeFinOp,progHistory,1);
  const wtd=calcVariation(soldeFinOp,progHistory,7);
  const mtd=calcVariation(soldeFinOp,progHistory,30);
  const qtd=calcVariation(soldeFinOp,progHistory,90);
  const hasVariations=j1||wtd||mtd||qtd;
  // État de la modale : null ou { variation, periodLabel }
  const [modal, setModal] = useState(null);
  return(
    <div onClick={()=>onSelect(proj.id)}
      style={{background:"#fff",borderRadius:12,border:"1px solid #e2e8f0",padding:16,cursor:"pointer",
        boxShadow:"0 1px 4px #0001",transition:"box-shadow .15s"}}
      onMouseEnter={e=>e.currentTarget.style.boxShadow="0 4px 16px #0002"}
      onMouseLeave={e=>e.currentTarget.style.boxShadow="0 1px 4px #0001"}>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start",marginBottom:8}}>
        <div>
          <div style={{fontWeight:800,fontSize:14,color:"#1e293b",marginBottom:2}}>
            {proj.ville?proj.ville+" — ":""}{proj.nom}
          </div>
          <span style={{background:statut.bg,color:statut.color,borderRadius:10,padding:"2px 10px",
            fontSize:11,fontWeight:700}}>
            <span style={{width:7,height:7,borderRadius:"50%",background:statut.dot,
              display:"inline-block",marginRight:5,verticalAlign:"middle"}}/>
            {statut.label}
          </span>
        </div>
        <button onClick={e=>{e.stopPropagation();onDelete(proj.id);}}
          style={{background:"none",border:"none",cursor:"pointer",color:"#94a3b8",fontSize:16,padding:4}}
          title="Supprimer">🗑</button>
      </div>
      {nbLots>0&&(
        <div style={{fontSize:12,color:"#64748b",marginBottom:8}}>
          {nbVendus}/{nbLots} lot{nbLots>1?"s":""} vendu{nbVendus>1?"s":""}
        </div>
      )}
      <div style={{paddingTop:10,borderTop:"1px solid #e2e8f0"}}>
        <div style={{fontWeight:700,color:soldeFinOp>=0?"#2563eb":"#dc2626",fontSize:13,marginBottom:hasVariations?4:0}}>
          {fmtEur(soldeFinOp)} <span style={{fontSize:10,color:"#94a3b8",fontWeight:400}}>fin d'opération</span>
        </div>
        {hasVariations&&(
          <div style={{display:"flex",flexWrap:"wrap",gap:"6px 10px"}}>
            <VariationPuce variation={j1}  label="24H"  onClick={()=>setModal({variation:j1,  periodLabel:'24H'})}/>
            <VariationPuce variation={wtd} label="SEM"  onClick={()=>setModal({variation:wtd, periodLabel:'sur 7 jours'})}/>
            <VariationPuce variation={mtd} label="MOIS" onClick={()=>setModal({variation:mtd, periodLabel:'sur 30 jours'})}/>
            <VariationPuce variation={qtd} label="TRIM" onClick={()=>setModal({variation:qtd, periodLabel:'sur 90 jours'})}/>
          </div>
        )}
      </div>
      {modal && (
        <VariationDetailsModal
          proj={proj}
          periodLabel={modal.periodLabel}
          variation={modal.variation}
          currentSolde={soldeFinOp}
          onClose={()=>setModal(null)}/>
      )}
    </div>
  );
}

function ProgrammesView({projects,fiches,values,rows,onSelect,onCreate,onDelete}) {
  const [showNew,setShowNew]=useState(false);
  const [history,setHistory]=useState([]);

  // Charge l'historique solde au montage (silencieux si vide — les snapshots n'existent pas encore)
  React.useEffect(()=>{
    fetch('/api/solde-history')
      .then(r=>r.ok?r.json():[])
      .then(data=>setHistory(Array.isArray(data)?data:[]))
      .catch(()=>{});
  },[]);

  const projs=projects.filter(p=>!p.isGlobal);
  const grouped=STATUT_GROUPES.map(sg=>({
    ...sg,
    items:projs.filter(p=>{const s=(fiches[p.id]||EMPTY_FICHE()).statut;return s===sg.key;})
  })).filter(g=>g.items.length>0);
  return(
    <div style={{padding:20,maxWidth:960,margin:"0 auto"}}>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:20}}>
        <div style={{fontWeight:800,fontSize:20,color:"#1e293b"}}>Programmes</div>
        <button onClick={()=>setShowNew(true)}
          style={{background:"#2563eb",color:"#fff",border:"none",borderRadius:8,
            padding:"8px 18px",fontSize:13,fontWeight:700,cursor:"pointer"}}>+ Nouveau programme</button>
      </div>
      {grouped.length===0&&(
        <div style={{textAlign:"center",padding:48,color:"#94a3b8",fontSize:14}}>
          Aucun programme. Cliquez sur "+ Nouveau programme" pour commencer.
        </div>
      )}
      {grouped.map(g=>(
        <div key={g.key} style={{marginBottom:28}}>
          <div style={{fontWeight:700,fontSize:13,color:g.color,marginBottom:10,
            display:"flex",alignItems:"center",gap:8}}>
            <span style={{width:9,height:9,borderRadius:"50%",background:g.dot,display:"inline-block"}}/>
            {g.label}
            <span style={{background:g.bg,borderRadius:10,padding:"1px 8px",fontSize:11}}>{g.items.length}</span>
          </div>
          <div style={{display:"grid",gridTemplateColumns:"repeat(auto-fill,minmax(260px,1fr))",gap:12}}>
            {g.items.map(p=>{
              const soldeFinOp=values.filter(v=>{const r=rows.find(r=>r.id===v.rowId);return r&&r.projetId===p.id&&!r.hidden;}).reduce((s,v)=>s+v.montant,0);
              return(
                <ProgrammeCard key={p.id} proj={p} fiche={fiches[p.id]||EMPTY_FICHE()}
                  soldeFinOp={soldeFinOp} history={history} onSelect={onSelect} onDelete={onDelete}/>
              );
            })}
          </div>
        </div>
      ))}
      {showNew&&<NewProgrammeModal onCreate={(v,n)=>{onCreate(v,n);setShowNew(false);}} onClose={()=>setShowNew(false)}/>}
    </div>
  );
}
