/* ── NotificationsView : onglet Notifications & Actions ── */
function NotificationsView({notifications,setNotifications,notifLoading,setNotifLoading,notifError,setNotifError,notifFilterBal,setNotifFilterBal,notifFilterCat,setNotifFilterCat,notifShowAll,setNotifShowAll,notifPdfModal,setNotifPdfModal,analyzingIds,setAnalyzingIds,editingFacture,setEditingFacture,reglerModal,setReglerModal,agentRunning,setAgentRunning,loadNotifs,fiches,projects,rows,values,handleCellSave,crm}) {
  const inp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"8px 12px",fontSize:13,width:"100%"};
  const btnS=(bg,sm)=>({background:bg||"#2563eb",color:"#fff",border:"none",borderRadius:7,padding:sm?"4px 10px":"8px 16px",cursor:"pointer",fontSize:sm?11:13,fontWeight:600});

  const CAT_COLORS={
    facture:               {bg:"#dbeafe",color:"#1d4ed8"},
    notaire:               {bg:"#f3e8ff",color:"#7c3aed"},
    architecte:            {bg:"#d1fae5",color:"#065f46"},
    banque:                {bg:"#ffedd5",color:"#c2410c"},
    contrat:               {bg:"#fef9c3",color:"#713f12"},
    lia_retour:            {bg:"#e0f2fe",color:"#0369a1"},
    eg_appel_fonds:        {bg:"#fef3c7",color:"#b45309"},
    architecte_attestation:{bg:"#ecfdf5",color:"#047857"},
    autre:                 {bg:"#f1f5f9",color:"#64748b"},
  };
  const FACTURE_STATUS_CONFIG={
    "reçue":    {bg:"#dbeafe",color:"#1d4ed8",label:"Reçue"},
    "confirmée":{bg:"#dcfce7",color:"#15803d",label:"Confirmée"},
    "réglée":   {bg:"#f1f5f9",color:"#64748b",label:"Réglée ✓"},
    "refusée":  {bg:"#fee2e2",color:"#dc2626",label:"Refusée"},
  };
  const relTime=(iso)=>{
    const d=Date.now()-new Date(iso).getTime();
    if(d<60000) return "à l'instant";
    if(d<3600000) return `il y a ${Math.round(d/60000)} min`;
    if(d<86400000) return `il y a ${Math.round(d/3600000)} h`;
    return `il y a ${Math.round(d/86400000)} j`;
  };

  // Séparation factures / autres
  const filteredByBal=notifications.filter(n=>notifFilterBal==="all"||n.mailbox===notifFilterBal);
  const factures=filteredByBal.filter(n=>n.category==="facture");
  const autres=filteredByBal.filter(n=>n.category!=="facture"&&(notifFilterCat==="all"||n.category===notifFilterCat));

  // Factures visibles selon l'état du filtre
  const facturesVisible=notifShowAll
    ? factures
    : factures.filter(n=>!["réglée","refusée"].includes(n.facture_status)&&n.status!=="dismissed");

  // Autres notifs visibles
  const autresVisible=notifShowAll
    ? autres
    : autres.filter(n=>n.status==="new");

  const analyzeFacture=async(notifId)=>{
    setAnalyzingIds(s=>({...s,[notifId]:true}));
    try{
      const r=await fetch("/api/agent/notifications/"+notifId+"/read-invoice",{method:"POST"});
      const d=await r.json();
      if(d.error) alert("Erreur analyse : "+d.error);
      else loadNotifs(notifShowAll);
    }catch(e){ alert("Erreur réseau : "+e.message); }
    finally{ setAnalyzingIds(s=>({...s,[notifId]:false})); }
  };

  const patchFactureStatus=async(notifId,status)=>{
    await fetch("/api/agent/notifications/"+notifId+"/facture",{
      method:"PATCH",headers:{"Content-Type":"application/json"},
      body:JSON.stringify({facture_status:status})
    }).catch(()=>{});
    loadNotifs(notifShowAll);
  };

  const saveFactureData=async(notifId,data)=>{
    await fetch("/api/agent/notifications/"+notifId+"/facture",{
      method:"PATCH",headers:{"Content-Type":"application/json"},
      body:JSON.stringify({facture_data:data,facture_status:"confirmée"})
    }).catch(()=>{});
    /* ── Envoi email GFA si prévalidation activée ── */
    try {
      const notif=notifications.find(n=>n.id===notifId);
      const programmeId=notif?.proposed_action?.programmeId||notif?.proposed_action?.params?.programmeId;
      const ficheProg=programmeId?fiches[programmeId]:null;
      if(ficheProg?.prevalidationGFA && ficheProg?.gfaEmailContact) {
        const gfaEmail=ficheProg.gfaEmailContact;
        /* Récupérer le PDF en base64 */
        let attachmentPayload=null;
        try {
          const pdfResp=await fetch("/api/agent/notifications/"+notifId+"/attachment");
          if(pdfResp.ok){
            const blob=await pdfResp.blob();
            const base64=await new Promise(resolve=>{
              const reader=new FileReader();
              reader.onloadend=()=>resolve(reader.result.split(",")[1]);
              reader.readAsDataURL(blob);
            });
            let filename="facture.pdf";
            try{const ref=JSON.parse(notif.attachment_ref||"{}");filename=ref.filename||filename;}catch(_){}
            attachmentPayload={filename,contentType:"application/pdf",content:base64};
          }
        } catch(_) {}
        const fournisseur=data.fournisseur||"(fournisseur inconnu)";
        const montant=data.montant_ttc!=null?Number(data.montant_ttc).toLocaleString("fr-FR",{minimumFractionDigits:2,maximumFractionDigits:2}):"?";
        const numFact=data.numero_facture||"";
        const subject="Prévalidation facture — "+fournisseur+" — "+montant+" €";
        const body="Bonjour,\n\nVeuillez trouver ci-joint la facture de "+fournisseur+" d'un montant de "+montant+" € TTC"+(numFact?" (réf. "+numFact+")":"")+` pour prévalidation.\n\nCordialement,\nPierre Rouzaud — TrésoImmo`;
        const emailPayload={to:gfaEmail,subject,body};
        if(attachmentPayload) emailPayload.attachment=attachmentPayload;
        const emailResp=await fetch("/api/email/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(emailPayload)});
        if(emailResp.ok){
          setTimeout(()=>alert("📧 Email GFA envoyé à "+gfaEmail),100);
        } else {
          const errTxt=await emailResp.text().catch(()=>"");
          setTimeout(()=>alert("⚠️ Impossible d'envoyer l'email GFA à "+gfaEmail+".\n"+errTxt),100);
        }
      }
    } catch(e) {
      console.warn("Envoi email GFA échoué (non-bloquant) :", e);
    }
    setEditingFacture(null);
    loadNotifs(notifShowAll);
  };

  const resetFacture=async(notifId)=>{
    if(!confirm("Réinitialiser cette facture à l'état « Non analysée » ?")) return;
    await fetch("/api/agent/notifications/"+notifId,{
      method:"PATCH",headers:{"Content-Type":"application/json"},
      body:JSON.stringify({action:"reset_facture"})
    }).catch(()=>{});
    loadNotifs(notifShowAll);
  };

  const executeNotifAction=async(notifId,actionType,pathOverride)=>{
    try{
      const body={action_type:actionType,confirmed:true};
      if(pathOverride) body.oneDrivePath=pathOverride;
      const r=await fetch("/api/agent/notifications/"+notifId+"/execute",{
        method:"POST",headers:{"Content-Type":"application/json"},
        body:JSON.stringify(body)
      });
      const d=await r.json();
      if(d.error){ alert("Erreur exécution : "+d.error); return false; }
      loadNotifs(notifShowAll);
      return true;
    }catch(e){ alert("Erreur réseau : "+e.message); return false; }
  };

  // ── Re-classification / forçage manuel ───────────────────────────────────
  const [reclassifyingIds,setReclassifyingIds]=React.useState({});
  // forceCatState : { [notifId]: selectedCategory }
  const [forceCatState,setForceCatState]=React.useState({});
  const [forcingIds,setForcingIds]=React.useState({});

  const ALL_CATS=['facture','eg_appel_fonds','notaire','architecte',
                  'architecte_attestation','banque','contrat','lia_retour','autre'];

  const reclassify=async(notifId)=>{
    setReclassifyingIds(s=>({...s,[notifId]:true}));
    try{
      const r=await fetch("/api/agent/notifications/"+notifId+"/reclassify",{method:"POST"});
      const d=await r.json();
      if(d.error) alert("Erreur re-classification : "+d.error);
      else {
        const msg=d.new_category===d.old_category
          ?"Résultat inchangé : "+d.new_category+" ("+d.confidence+")"
          :"✓ Re-classifié : "+d.old_category+" → "+d.new_category+" ("+d.confidence+")";
        alert(msg);
        loadNotifs(notifShowAll);
      }
    }catch(e){alert("Erreur réseau : "+e.message);}
    setReclassifyingIds(s=>({...s,[notifId]:false}));
  };

  const forceCategory=async(notifId)=>{
    const cat=forceCatState[notifId];
    if(!cat){alert("Sélectionnez d'abord une catégorie.");return;}
    setForcingIds(s=>({...s,[notifId]:true}));
    try{
      const r=await fetch("/api/agent/notifications/"+notifId,{
        method:"PATCH",headers:{"Content-Type":"application/json"},
        body:JSON.stringify({action:"force_category",category:cat})
      });
      const d=await r.json();
      if(d.error) alert("Erreur : "+d.error);
      else{ loadNotifs(notifShowAll); setForceCatState(s=>({...s,[notifId]:undefined})); }
    }catch(e){alert("Erreur réseau : "+e.message);}
    setForcingIds(s=>({...s,[notifId]:false}));
  };

  // État de l'éditeur inline de chemin OneDrive (un seul ouvert à la fois)
  // { notifId, actionType, folder, file, programmeRoot,
  //   checking, checkResult, browserOpen, browserPath, browserData }
  const [archiveInline,setArchiveInline]=React.useState(null);

  // ── Modale LIA incomplète ──────────────────────────────────────────────────
  // { notifId, loading, emailData: { to, cc, subject, body, attachment, hasAttachment } }
  const [incompleteLiaModal,setIncompleteLiaModal]=React.useState(null);

  // ── Modale Convertir en appel de fonds ────────────────────────────────────
  // { notif } — ouverte pour les notifs eg_appel_fonds
  const [convertirModal,setConvertirModal]=React.useState(null);

  // ── Modale Générer appel client ────────────────────────────────────────────
  // { notif } — ouverte pour les notifs architecte_attestation
  const [appelClientModal,setAppelClientModal]=React.useState(null);

  // ── Modale contexte attestation (s'ouvre EN PREMIER — lit le PDF via Haiku) ──
  const [attestContextModal,setAttestContextModal]=React.useState(null);

  // ── Modale archivage attestation (s'ouvre APRÈS attestContextModal) ──────────
  const [attestArchiveModal,setAttestArchiveModal]=React.useState(null);

  const openIncompleteLiaModal=async(notifId)=>{
    setIncompleteLiaModal({notifId,loading:true,emailData:null});
    try{
      const r=await fetch("/api/agent/notifications/"+notifId+"/execute",{
        method:"POST",headers:{"Content-Type":"application/json"},
        body:JSON.stringify({action_type:"send_incomplete_lia_email",confirmed:true})
      });
      const d=await r.json();
      if(d.error){alert("Erreur préparation email : "+d.error);setIncompleteLiaModal(null);return;}
      setIncompleteLiaModal({notifId,loading:false,emailData:d.emailData});
    }catch(e){alert("Erreur réseau : "+e.message);setIncompleteLiaModal(null);}
  };

  /* ── Ouvre l'éditeur inline pour une notification ── */
  const openArchiveInline=async(notifId,action,actionType)=>{
    const fp=action?.params?.oneDrivePath||'';
    const last=fp.lastIndexOf('\\');
    const folder=last>=0?fp.slice(0,last):'';
    const file=last>=0?fp.slice(last+1):fp;
    const segs=fp.replace(/\\/g,'/').split('/').filter(Boolean);
    const programmeRoot=segs.length>=4?'\\'+segs.slice(0,4).join('\\'):fp;
    setArchiveInline({notifId,actionType,folder,file,programmeRoot,
      checking:true,checkResult:null,browserOpen:false,browserPath:'',browserData:null});
    try{
      const r=await fetch('/api/agent/onedrive/browse?path='+encodeURIComponent(folder));
      const d=await r.json();
      setArchiveInline(p=>p?.notifId===notifId?{...p,checking:false,checkResult:d}:p);
    }catch(e){
      setArchiveInline(p=>p?.notifId===notifId?{...p,checking:false,checkResult:{exists:false,folders:[],error:e.message}}:p);
    }
  };

  /* ── Vérifie / re-vérifie le chemin courant ── */
  const recheckArchiveFolder=async(path)=>{
    if(!path)return;
    setArchiveInline(p=>p?{...p,checking:true,checkResult:null}:p);
    try{
      const r=await fetch('/api/agent/onedrive/browse?path='+encodeURIComponent(path));
      const d=await r.json();
      setArchiveInline(p=>p?{...p,checking:false,checkResult:d}:p);
    }catch(e){
      setArchiveInline(p=>p?{...p,checking:false,checkResult:{exists:false,folders:[],error:e.message}}:p);
    }
  };

  /* ── Navigue dans un dossier OneDrive ── */
  const loadArchiveBrowser=async(path)=>{
    setArchiveInline(p=>p?{...p,browserOpen:true,browserPath:path,browserData:{folders:[],loading:true}}:p);
    try{
      const r=await fetch('/api/agent/onedrive/browse?path='+encodeURIComponent(path));
      const d=await r.json();
      setArchiveInline(p=>p?{...p,browserData:{folders:d.folders||[],loading:false}}:p);
    }catch(e){
      setArchiveInline(p=>p?{...p,browserData:{folders:[],loading:false,error:e.message}}:p);
    }
  };

  /* ── Sélectionne un dossier depuis le navigateur ── */
  const selectArchiveBrowserFolder=async()=>{
    const selectedPath=archiveInline?.browserPath;
    if(!selectedPath)return;
    setArchiveInline(p=>p?{...p,folder:selectedPath,browserOpen:false,checkResult:null,checking:true}:p);
    try{
      const r=await fetch('/api/agent/onedrive/browse?path='+encodeURIComponent(selectedPath));
      const d=await r.json();
      setArchiveInline(p=>p?{...p,checking:false,checkResult:d}:p);
    }catch(e){
      setArchiveInline(p=>p?{...p,checking:false,checkResult:{exists:false,folders:[],error:e.message}}:p);
    }
  };

  return (
  <div style={{padding:24,maxWidth:860,margin:"0 auto"}}>

    {/* ── Header + filtres ── */}
    <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:20,flexWrap:"wrap",gap:8}}>
      <div style={{fontWeight:800,fontSize:18}}>🔔 Notifications & Actions</div>
      <div style={{display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"}}>
        {/* Bouton refresh : déclenche un run de l'agent (nouveaux emails depuis
            le dernier curseur de scan) puis recharge les notifications après un
            délai. Le verrou PID de l'agent évite les exécutions concurrentes. */}
        <button
          onClick={async()=>{
            if(notifLoading||agentRunning) return;
            setAgentRunning(true);
            try{
              const r=await fetch("/api/agent/trigger",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({resetCursor:false})});
              const d=await r.json();
              if(!r.ok||d.error){
                alert("Erreur déclenchement agent : "+(d.error||r.status));
                loadNotifs(notifShowAll);
              }else{
                // Recharge auto une première fois rapidement (notifs déjà en DB)
                // puis une seconde fois après que le scan a eu le temps de finir.
                setTimeout(()=>loadNotifs(notifShowAll),2000);
                setTimeout(()=>loadNotifs(notifShowAll),10000);
              }
            }catch(e){
              alert("Erreur réseau : "+e.message);
              loadNotifs(notifShowAll);
            }
            finally{setTimeout(()=>setAgentRunning(false),11000);}
          }}
          disabled={notifLoading||agentRunning}
          title="Actualiser et scanner les nouveaux emails"
          style={{background:"none",border:"1px solid "+C.border,borderRadius:6,padding:"4px 9px",fontSize:13,cursor:(notifLoading||agentRunning)?"default":"pointer",color:C.muted,lineHeight:1}}>
          {agentRunning?"⏳":"🔄"}
        </button>
        <button onClick={()=>setNotifShowAll(v=>!v)} style={btnS(notifShowAll?"#334155":"#2563eb",true)}>
          {notifShowAll?"Actives uniquement":"Tout afficher (archivées)"}
        </button>
        <select value={notifFilterBal} onChange={e=>setNotifFilterBal(e.target.value)}
          style={{...inp,width:"auto",fontSize:12,padding:"4px 8px"}}>
          <option value="all">Toutes les BAL</option>
          {[...new Set(notifications.map(n=>n.mailbox))].map(m=><option key={m} value={m}>{m}</option>)}
        </select>
      </div>
    </div>

    {notifError&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"10px 14px",marginBottom:16,fontSize:12,color:"#dc2626"}}>⚠️ Impossible de charger les notifications : {notifError}</div>}
    {notifLoading&&<div style={{textAlign:"center",color:C.muted,padding:40}}>Chargement…</div>}

    {!notifLoading&&(
    <>
      {/* ══ SECTION FACTURES ══ */}
      <div style={{marginBottom:28}}>
        <div style={{display:"flex",alignItems:"center",gap:10,marginBottom:12}}>
          <div style={{fontWeight:700,fontSize:15}}>🧾 Factures</div>
          {factures.filter(n=>n.facture_status==="confirmée").length>0&&(
            <span style={{background:"#fef3c7",color:"#b45309",borderRadius:10,padding:"1px 8px",fontSize:11,fontWeight:700}}>
              {factures.filter(n=>n.facture_status==="confirmée").length} à régler
            </span>
          )}
          {factures.filter(n=>!n.facture_data&&n.status!=="dismissed").length>0&&(
            <span style={{background:"#dbeafe",color:"#1d4ed8",borderRadius:10,padding:"1px 8px",fontSize:11,fontWeight:700}}>
              {factures.filter(n=>!n.facture_data&&n.status!=="dismissed").length} à analyser
            </span>
          )}
        </div>

        {facturesVisible.length===0&&(
          <div style={{background:C.card,borderRadius:10,border:"1px dashed "+C.border,padding:24,textAlign:"center",color:C.muted,fontSize:13}}>
            {notifShowAll?"Aucune facture reçue.":"Aucune facture en attente de traitement. 🎉"}
          </div>
        )}

        {facturesVisible.map(notif=>{
          let ref=null;
          try{if(notif.attachment_ref)ref=JSON.parse(notif.attachment_ref);}catch(e){}
          const fdRaw=notif.facture_data;
          // Normalise : l'ancien format est un objet, le nouveau un tableau
          const fdArray=Array.isArray(fdRaw)?fdRaw:(fdRaw?[fdRaw]:[]);
          const fd=fdArray[0]||null;
          const isMultiFacture=fdArray.length>1;
          const fs=notif.facture_status;
          const fsCfg=FACTURE_STATUS_CONFIG[fs]||null;
          const pdfTmp=notif.pdf_tmp_path;
          const pdfOd=notif.pdf_onedrive_url;
          const isArchived=["réglée","refusée"].includes(fs)||notif.status==="dismissed";
          const isEditing=editingFacture?.id===notif.id;

          return (
            <div key={notif.id} style={{background:C.card,borderRadius:10,border:"1px solid "+(isArchived?"#e2e8f0":C.border),padding:16,marginBottom:10,opacity:isArchived?0.65:1}}>
              <div style={{display:"flex",alignItems:"center",gap:6,marginBottom:8,flexWrap:"wrap"}}>
                <span style={{background:"#dbeafe",color:"#1d4ed8",borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:700}}>FACTURE</span>
                {fsCfg&&<span style={{background:fsCfg.bg,color:fsCfg.color,borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:700}}>{fsCfg.label}</span>}
                {!fs&&!fd&&<span style={{background:"#fef9c3",color:"#92400e",borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:600}}>Non analysée</span>}
                {isMultiFacture&&<span style={{background:"#ede9fe",color:"#7c3aed",borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:700}}>{fdArray.length} factures</span>}
                <span style={{fontSize:11,color:C.muted,marginLeft:"auto"}}>{relTime(notif.received_at)}</span>
              </div>
              <div style={{fontSize:12,color:C.muted,marginBottom:2}}>De : {notif.from_name||""} {"<"+(notif.from_email||"")+">"}</div>
              <div style={{fontWeight:600,fontSize:13,marginBottom:8,color:C.text}}>{notif.subject}</div>
              {ref&&(
                <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:6,background:"#f8fafc",borderRadius:6,padding:"5px 10px"}}>
                  <span style={{fontSize:12}}>📎 {ref.filename||"document.pdf"}</span>
                  {ref.size&&<span style={{fontSize:11,color:C.muted}}>({Math.round(ref.size/1024)} Ko)</span>}
                  <button onClick={()=>setNotifPdfModal(notif.id)} style={btnS("#334155",true)}>aperçu PDF</button>
                </div>
              )}
              {(pdfTmp||pdfOd)&&(
                <div style={{display:"inline-flex",alignItems:"center",gap:6,marginBottom:10,padding:"3px 10px",borderRadius:6,
                  background:pdfOd?"#f0fdf4":"#eff6ff",
                  border:"1px solid "+(pdfOd?"#bbf7d0":"#bfdbfe"),
                  fontSize:11,color:pdfOd?"#15803d":"#1d4ed8",fontWeight:600}}>
                  {pdfOd
                    ? <>☁️ Archivé OneDrive&nbsp;
                        <a href={pdfOd} target="_blank" rel="noopener noreferrer"
                          style={{color:"#15803d",textDecoration:"underline",fontWeight:700}}>ouvrir ↗</a>
                      </>
                    : <>📎 PDF sauvegardé localement</>
                  }
                </div>
              )}
              {fdArray.length>0&&!isEditing&&fdArray.map((facture,fi)=>(
                <div key={fi} style={{background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:8,padding:12,marginBottom:isMultiFacture?6:10}}>
                  {isMultiFacture&&(
                    <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
                      <span style={{fontSize:11,fontWeight:700,color:"#7c3aed"}}>Facture {fi+1}/{fdArray.length}</span>
                      {fs==="confirmée"&&(
                        <button onClick={()=>setReglerModal({...notif,facture_data:facture})}
                          style={{background:"#2563eb",color:"#fff",border:"none",borderRadius:5,padding:"3px 10px",fontSize:11,cursor:"pointer",fontWeight:600}}>
                          💶 Régler cette facture
                        </button>
                      )}
                    </div>
                  )}
                  <div style={{display:"grid",gridTemplateColumns:"auto 1fr auto 1fr",gap:"3px 16px",fontSize:12,alignItems:"start"}}>
                    {facture.fournisseur&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap"}}>Fournisseur</span><span style={{fontWeight:700}}>{facture.fournisseur}</span></>}
                    {facture.numero_facture&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap"}}>N° facture</span><span>{facture.numero_facture}</span></>}
                    {facture.date_facture&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap"}}>Date</span><span>{new Date(facture.date_facture+"T12:00:00Z").toLocaleDateString("fr-FR")}</span></>}
                    {facture.montant_ht!=null&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap"}}>HT</span><span>{fmtEur(facture.montant_ht)}</span></>}
                    {facture.tva!=null&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap"}}>TVA</span><span>{fmtEur(facture.tva)}</span></>}
                    {facture.montant_ttc!=null&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap"}}>Montant TTC</span><span style={{fontWeight:800,color:"#1d4ed8",fontSize:13}}>{fmtEur(facture.montant_ttc)}</span></>}
                    {facture.description&&<><span style={{color:"#64748b",fontWeight:600,whiteSpace:"nowrap",gridColumn:"1"}}>Prestation</span><span style={{fontStyle:"italic",gridColumn:"2/-1",fontSize:11}}>{facture.description}</span></>}
                  </div>
                </div>
              ))}
              {isEditing&&(()=>{
                const ef=editingFacture.data;
                const inp2={width:"100%",padding:"4px 7px",border:"1px solid #e2e8f0",borderRadius:5,fontSize:12,background:"#f8fafc"};
                return (
                  <div style={{background:"#fffbeb",border:"1px solid #fde68a",borderRadius:8,padding:12,marginBottom:10}}>
                    <div style={{fontWeight:700,fontSize:12,color:"#92400e",marginBottom:8}}>✎ Corriger les données</div>
                    <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:6,fontSize:12}}>
                      {[
                        {k:"fournisseur",l:"Fournisseur"},
                        {k:"numero_facture",l:"N° facture"},
                        {k:"date_facture",l:"Date (AAAA-MM-JJ)"},
                        {k:"montant_ht",l:"Montant HT (€)"},
                        {k:"tva",l:"TVA (€)"},
                        {k:"montant_ttc",l:"Montant TTC (€)"},
                      ].map(({k,l})=>(
                        <div key={k}>
                          <div style={{fontSize:10,color:"#64748b",marginBottom:2,fontWeight:600}}>{l}</div>
                          <input value={ef[k]!=null?ef[k]:""} onChange={e=>{
                            const v=["montant_ht","tva","montant_ttc"].includes(k)?parseFloat(e.target.value)||e.target.value:e.target.value;
                            setEditingFacture(s=>({...s,data:{...s.data,[k]:v}}));
                          }} style={inp2}/>
                        </div>
                      ))}
                      <div style={{gridColumn:"1/-1"}}>
                        <div style={{fontSize:10,color:"#64748b",marginBottom:2,fontWeight:600}}>Prestation</div>
                        <input value={ef.description||""} onChange={e=>setEditingFacture(s=>({...s,data:{...s.data,description:e.target.value}}))} style={inp2}/>
                      </div>
                    </div>
                    <div style={{display:"flex",gap:6,marginTop:8,justifyContent:"flex-end"}}>
                      <button onClick={()=>setEditingFacture(null)} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:5,padding:"4px 12px",fontSize:11,cursor:"pointer"}}>Annuler</button>
                      <button onClick={()=>saveFactureData(notif.id,editingFacture.data)} style={{background:"#16a34a",color:"#fff",border:"none",borderRadius:5,padding:"4px 12px",fontSize:11,cursor:"pointer",fontWeight:600}}>✓ Confirmer les corrections</button>
                    </div>
                  </div>
                );
              })()}
              {!isArchived&&(
                <div style={{display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"}}>
                  {!fd&&notif.attachment_ref&&(
                    <button onClick={()=>analyzeFacture(notif.id)} disabled={!!analyzingIds[notif.id]}
                      style={btnS(analyzingIds[notif.id]?"#94a3b8":"#7c3aed",true)}>
                      {analyzingIds[notif.id]?"⏳ Analyse en cours…":"🔍 Analyser la facture"}
                    </button>
                  )}
                  {!fd&&!notif.attachment_ref&&(
                    <div style={{display:"flex",alignItems:"center",gap:8}}>
                      <span style={{fontSize:11,color:"#94a3b8",fontStyle:"italic"}}>📭 Aucune PJ — saisie manuelle</span>
                      <button onClick={()=>setReglerModal(notif)}
                        style={btnS("#2563eb",true)}>💶 Saisir & intégrer au plan</button>
                    </div>
                  )}
                  {fd&&fs==="reçue"&&!isEditing&&(
                    <>
                      <button onClick={()=>patchFactureStatus(notif.id,"confirmée")} style={btnS("#16a34a",true)}>✓ Confirmer</button>
                      <button onClick={()=>setEditingFacture({id:notif.id,data:{...fd}})} style={btnS("#f59e0b",true)}>✎ Corriger</button>
                      <button onClick={()=>patchFactureStatus(notif.id,"refusée")} style={btnS("#64748b",true)}>✕ Refuser</button>
                    </>
                  )}
                  {fd&&fs==="confirmée"&&!isMultiFacture&&(
                    <button onClick={()=>setReglerModal(notif)} style={btnS("#2563eb",true)}>💶 Régler & intégrer au plan</button>
                  )}
                  {!fd&&(
                    <button onClick={async()=>{
                      await fetch("/api/agent/notifications/"+notif.id,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"dismiss"})}).catch(()=>{});
                      loadNotifs(notifShowAll);
                    }} style={{background:"none",border:"1px solid #e2e8f0",borderRadius:6,padding:"4px 10px",fontSize:11,cursor:"pointer",color:C.muted}}>Écarter</button>
                  )}
                  {fd&&(
                    <button onClick={()=>resetFacture(notif.id)}
                      style={{background:"none",border:"1px solid #fca5a5",borderRadius:6,padding:"4px 10px",fontSize:11,cursor:"pointer",color:"#dc2626"}}>↺ Réinitialiser</button>
                  )}
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* ══ SECTION AUTRES NOTIFICATIONS ══ */}
      {(notifFilterCat==="all"||notifFilterCat!=="facture")&&autresVisible.length>0&&(
        <div>
          <div style={{fontWeight:700,fontSize:14,marginBottom:10,color:C.muted,paddingTop:8,borderTop:"1px solid "+C.border}}>
            📬 Autres notifications
            <select value={notifFilterCat} onChange={e=>setNotifFilterCat(e.target.value)}
              style={{...inp,width:"auto",fontSize:11,padding:"2px 6px",marginLeft:10}}>
              <option value="all">Toutes catégories</option>
              {["notaire","architecte","architecte_attestation","lia_retour","banque","contrat","eg_appel_fonds","autre"].map(c=><option key={c} value={c}>{c}</option>)}
            </select>
          </div>
          {autresVisible.map(notif=>{
            const cat=CAT_COLORS[notif.category]||CAT_COLORS.autre;
            let ref=null;
            try{if(notif.attachment_ref)ref=JSON.parse(notif.attachment_ref);}catch(e){}
            const action=notif.proposed_action;
            const isLow=notif.confidence==="rule_low";
            const meta=notif.metadata||{};
            const docType=action?.params?.docType||null;
            const avancPct=meta.avancement_pct;
            const isArchiveType=["archive_document","archive_and_update_statut"].includes(action?.type);
            const canExecute=!isLow&&isArchiveType;
            const isEditing=canExecute&&archiveInline?.notifId===notif.id;

            /* ── Titre / icône enrichis selon catégorie ── */
            let displayIcon="";
            let displayTitle=null;
            if(notif.category==="lia_retour"){
              displayIcon="📝 "; displayTitle="LIA signée reçue";
            } else if(notif.category==="notaire"){
              if(docType==="compromis_a_signer"){displayIcon="⚖️ "; displayTitle="Compromis reçu du notaire";}
              else if(docType==="acte_authentique"){displayIcon="🏛️ "; displayTitle="Acte authentique reçu";}
            } else if(notif.category==="architecte"&&avancPct!=null){
              const lotIdx=action?.params?.lotIdx;
              displayIcon="📐 "; displayTitle=`Attestation ${avancPct}% reçue${lotIdx!=null?` — Lot ${lotIdx+1}`:""}`;
            }

            const dismiss=async()=>{
              await fetch("/api/agent/notifications/"+notif.id,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"dismiss"})}).catch(()=>{});
              loadNotifs(notifShowAll);
            };
            const validate=async()=>{
              await fetch("/api/agent/notifications/"+notif.id,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"validate"})}).catch(()=>{});
              loadNotifs(notifShowAll);
            };
            const confirmArchive=async()=>{
              if(!archiveInline)return;
              const path=archiveInline.folder.replace(/\\+$/,'')+'\\'+ archiveInline.file;
              const ok=await executeNotifAction(notif.id,archiveInline.actionType,path);
              if(ok)setArchiveInline(null);
            };

            return (
              <div key={notif.id} style={{background:C.card,borderRadius:10,border:"1px solid "+(isLow?"#fed7aa":C.border),padding:14,marginBottom:10}}>

                {/* ── Badges ── */}
                <div style={{display:"flex",alignItems:"center",gap:6,marginBottom:6,flexWrap:"wrap"}}>
                  <span style={{background:cat.bg,color:cat.color,borderRadius:4,padding:"2px 8px",fontSize:11,fontWeight:700}}>
                    {displayIcon}{notif.category.toUpperCase()}
                  </span>
                  {notif.confidence==="ai"&&<span style={{background:"#fef3c7",color:"#b45309",borderRadius:4,padding:"1px 6px",fontSize:10,fontWeight:600}}>Claude Haiku</span>}
                  {isLow&&<span style={{background:"#ffedd5",color:"#c2410c",borderRadius:4,padding:"1px 6px",fontSize:10,fontWeight:700}}>⚠ à vérifier</span>}
                  {notif.category==="facture"&&notif.metadata?.doublon_probable&&(
                    <span style={{background:"#fff7ed",color:"#c2410c",borderRadius:4,padding:"1px 6px",fontSize:10,fontWeight:700}}
                      title={"Facture similaire reçue le "+new Date(notif.metadata.doublon_ref_date).toLocaleDateString("fr-FR")}
                    >⚠ doublon probable</span>
                  )}
                  <span style={{fontSize:11,color:C.muted,marginLeft:"auto"}}>{relTime(notif.received_at)}</span>
                </div>

                {/* ── Expéditeur ── */}
                <div style={{fontSize:12,color:C.muted,marginBottom:2}}>De : {notif.from_name||""} {"<"+(notif.from_email||"")+">"}</div>

                {/* ── Titre ── */}
                <div style={{fontWeight:700,fontSize:13,marginBottom:displayTitle?2:6,color:C.text}}>{displayTitle||notif.subject}</div>
                {displayTitle&&<div style={{fontSize:11,color:C.muted,marginBottom:4,fontStyle:"italic"}}>{notif.subject}</div>}
                {notif.category==="lia_retour"&&action?.params?.senderNom&&(
                  <div style={{fontSize:12,color:"#0369a1",marginBottom:6,fontWeight:600}}>👤 Envoyé par : {action.params.senderNom}</div>
                )}

                {/* ── Extrait du mail ── */}
                {notif.preview&&<div style={{fontSize:12,color:C.text,marginBottom:8,fontStyle:"italic",borderLeft:"3px solid #e2e8f0",paddingLeft:8,lineHeight:1.5}}>{notif.preview}</div>}

                {/* ── Pièce jointe ── */}
                {ref&&(
                  <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:8,background:"#f8fafc",borderRadius:6,padding:"5px 10px"}}>
                    <span style={{fontSize:12}}>📎 {ref.filename||"document.pdf"}</span>
                    {ref.size&&<span style={{fontSize:11,color:C.muted}}>({Math.round(ref.size/1024)} Ko)</span>}
                    <button onClick={()=>setNotifPdfModal(notif.id)} style={btnS("#334155",true)}>aperçu</button>
                  </div>
                )}

                {/* ── Action proposée — affichage statique OU éditeur inline ── */}
                {action&&action.type&&action.type!=="dismiss_or_manual"&&(
                  isEditing ? (
                    /* ══ ÉDITEUR INLINE DE CHEMIN ONEDRIVE ══ */
                    <div style={{background:"#f0f9ff",border:"1px solid #bae6fd",borderRadius:8,padding:12,marginBottom:8}}>
                      <div style={{fontWeight:700,fontSize:11,color:"#0369a1",marginBottom:8}}>📁 Chemin OneDrive — vérifier et corriger si besoin</div>

                      {/* Dossier */}
                      <div style={{marginBottom:6}}>
                        <div style={{fontSize:10,color:C.muted,fontWeight:600,marginBottom:3}}>Dossier</div>
                        <div style={{display:"flex",gap:5}}>
                          <input value={archiveInline.folder}
                            onChange={e=>setArchiveInline(p=>({...p,folder:e.target.value,checkResult:null}))}
                            style={{background:"#fff",border:"1px solid #bae6fd",borderRadius:6,color:"#1e293b",padding:"5px 8px",fontSize:11,flex:1,fontFamily:"monospace"}}/>
                          <button onClick={()=>recheckArchiveFolder(archiveInline.folder)} disabled={archiveInline.checking}
                            style={btnS("#64748b",true)}>{archiveInline.checking?"…":"🔍"}</button>
                          <button
                            onClick={()=>archiveInline.browserOpen
                              ?setArchiveInline(p=>({...p,browserOpen:false}))
                              :loadArchiveBrowser(archiveInline.programmeRoot)}
                            style={btnS(archiveInline.browserOpen?"#334155":"#0369a1",true)}>
                            {archiveInline.browserOpen?"↑ Fermer":"📂 Parcourir"}
                          </button>
                        </div>
                      </div>

                      {/* Résultat vérification */}
                      {archiveInline.checkResult&&(
                        <div style={{marginBottom:6,padding:"5px 9px",borderRadius:6,fontSize:11,
                          background:archiveInline.checkResult.exists?"#f0fdf4":"#fff7ed",
                          border:"1px solid "+(archiveInline.checkResult.exists?"#bbf7d0":"#fed7aa")}}>
                          {archiveInline.checkResult.exists
                            ?<span style={{color:"#15803d",fontWeight:700}}>✅ Dossier trouvé sur OneDrive</span>
                            :<>
                              <span style={{color:"#c2410c",fontWeight:700}}>⚠️ Dossier introuvable</span>
                              {archiveInline.checkResult.folders?.length>0&&(
                                <div style={{display:"flex",flexWrap:"wrap",gap:3,marginTop:4}}>
                                  {archiveInline.checkResult.folders.map(f=>(
                                    <button key={f.path}
                                      onClick={()=>{setArchiveInline(p=>({...p,folder:f.path,checkResult:null}));recheckArchiveFolder(f.path);}}
                                      style={{background:"#ffedd5",border:"1px solid #fed7aa",borderRadius:4,padding:"2px 7px",fontSize:10,cursor:"pointer",color:"#92400e",fontWeight:600}}>
                                      📁 {f.name}
                                    </button>
                                  ))}
                                </div>
                              )}
                              {archiveInline.checkResult.error&&<div style={{color:"#dc2626",marginTop:3,fontSize:10}}>{archiveInline.checkResult.error}</div>}
                            </>
                          }
                        </div>
                      )}

                      {/* Navigateur inline */}
                      {archiveInline.browserOpen&&(
                        <div style={{background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,padding:8,marginBottom:6}}>
                          {/* Fil d'Ariane */}
                          <div style={{fontSize:10,color:"#64748b",marginBottom:5,fontFamily:"monospace",wordBreak:"break-all",display:"flex",alignItems:"center",flexWrap:"wrap",gap:2}}>
                            {archiveInline.browserPath.split("\\").filter(Boolean).map((seg,i,arr)=>(
                              <span key={i} style={{display:"flex",alignItems:"center",gap:2}}>
                                {i>0&&<span style={{color:"#cbd5e1"}}>\</span>}
                                <span
                                  onClick={()=>{if(i<arr.length-1)loadArchiveBrowser("\\"+arr.slice(0,i+1).join("\\"));}}
                                  style={{cursor:i<arr.length-1?"pointer":"default",color:i<arr.length-1?"#2563eb":"#1e293b",fontWeight:i===arr.length-1?700:400,textDecoration:i<arr.length-1?"underline":"none"}}>
                                  {seg}
                                </span>
                              </span>
                            ))}
                          </div>
                          {/* Remonter */}
                          {archiveInline.browserPath&&archiveInline.browserPath!==archiveInline.programmeRoot&&(
                            <button onClick={()=>{
                              const segs=archiveInline.browserPath.replace(/\\+$/,"").split("\\").filter(Boolean);
                              segs.pop();
                              loadArchiveBrowser(segs.length?"\\"+segs.join("\\"):"\\");
                            }} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:5,padding:"3px 8px",fontSize:10,cursor:"pointer",color:"#334155",marginBottom:5}}>
                              ← Remonter
                            </button>
                          )}
                          {/* Liste dossiers */}
                          {!archiveInline.browserData||archiveInline.browserData.loading
                            ?<div style={{color:"#94a3b8",fontSize:11}}>Chargement…</div>
                            :archiveInline.browserData.error
                            ?<div style={{color:"#dc2626",fontSize:11}}>{archiveInline.browserData.error}</div>
                            :archiveInline.browserData.folders?.length===0
                            ?<div style={{color:"#94a3b8",fontSize:11,fontStyle:"italic"}}>Aucun sous-dossier</div>
                            :<div style={{display:"flex",flexDirection:"column",gap:2,maxHeight:160,overflowY:"auto"}}>
                              {archiveInline.browserData.folders.map(f=>(
                                <button key={f.path} onClick={()=>loadArchiveBrowser(f.path)}
                                  style={{background:"none",border:"1px solid #e2e8f0",borderRadius:5,padding:"4px 9px",fontSize:11,cursor:"pointer",textAlign:"left",color:"#1e293b",display:"flex",alignItems:"center",gap:6}}>
                                  <span>📁</span><span style={{flex:1}}>{f.name}</span><span style={{color:"#94a3b8",fontSize:10}}>▶</span>
                                </button>
                              ))}
                            </div>
                          }
                          {/* Sélectionner ce dossier */}
                          {archiveInline.browserData&&!archiveInline.browserData.loading&&(
                            <div style={{marginTop:6,paddingTop:6,borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"}}>
                              <button onClick={selectArchiveBrowserFolder} style={btnS("#0369a1",true)}>✓ Sélectionner ce dossier</button>
                            </div>
                          )}
                        </div>
                      )}

                      {/* Nom du fichier */}
                      <div>
                        <div style={{fontSize:10,color:C.muted,fontWeight:600,marginBottom:3}}>Nom du fichier</div>
                        <input value={archiveInline.file}
                          onChange={e=>setArchiveInline(p=>({...p,file:e.target.value}))}
                          style={{background:"#fff",border:"1px solid #bae6fd",borderRadius:6,color:"#1e293b",padding:"5px 8px",fontSize:11,width:"100%",boxSizing:"border-box"}}/>
                      </div>
                    </div>
                  ) : (
                    /* ══ AFFICHAGE STATIQUE ══ */
                    <div style={{background:"#eff6ff",borderRadius:6,padding:"6px 10px",marginBottom:8,fontSize:12,borderLeft:"3px solid "+C.accent}}>
                      <span style={{fontWeight:700}}>▸ </span><span>{action.label||action.type}</span>
                      {canExecute&&action.params?.oneDrivePath&&(
                        <div style={{display:"flex",alignItems:"center",gap:6,marginTop:4}}>
                          <div style={{fontSize:10,color:C.muted,fontFamily:"monospace",wordBreak:"break-all",flex:1}}>
                            📁 {action.params.oneDrivePath}
                          </div>
                          <button onClick={()=>openArchiveInline(notif.id,action,action.type)}
                            style={{background:"#0369a1",color:"#fff",border:"none",borderRadius:5,padding:"2px 8px",fontSize:10,cursor:"pointer",fontWeight:600,flexShrink:0}}>
                            📂 Vérifier
                          </button>
                        </div>
                      )}
                      {!canExecute&&action.params?.oneDrivePath&&(
                        <div style={{fontSize:10,color:C.muted,marginTop:3,fontFamily:"monospace",wordBreak:"break-all"}}>
                          📁 {action.params.oneDrivePath}
                        </div>
                      )}
                    </div>
                  )
                )}

                {/* ── Boutons d'action ── */}
                {notif.status==="new"&&(
                  <div style={{display:"flex",flexDirection:"column",gap:0}}>

                    {/* Ligne 1 : boutons principaux */}
                    <div style={{display:"flex",gap:6,flexWrap:"wrap"}}>
                      {isEditing&&(
                        <>
                          <button onClick={confirmArchive} disabled={!archiveInline?.file||!archiveInline?.folder}
                            style={btnS(!archiveInline?.file||!archiveInline?.folder?"#94a3b8":"#16a34a",true)}>
                            ✓ Confirmer l'archivage
                          </button>
                          {action.type==="archive_and_update_statut"&&(
                            <button onClick={()=>{setArchiveInline(null);openIncompleteLiaModal(notif.id);}}
                              style={btnS("#f59e0b",true)}>⚠ Document incomplet</button>
                          )}
                          <button onClick={()=>setArchiveInline(null)} style={btnS("#94a3b8",true)}>✕ Annuler</button>
                        </>
                      )}
                      {!isEditing&&canExecute&&action.type==="archive_and_update_statut"&&(
                        <button onClick={()=>openArchiveInline(notif.id,action,"archive_and_update_statut")}
                          style={btnS("#0369a1",true)}>✓ Archiver + passer en Sous LIA</button>
                      )}
                      {!isEditing&&canExecute&&action.type==="archive_document"&&docType==="compromis_a_signer"&&(
                        <button onClick={()=>openArchiveInline(notif.id,action,"archive_document")}
                          style={btnS("#7c3aed",true)}>✓ Archiver le compromis</button>
                      )}
                      {!isEditing&&canExecute&&action.type==="archive_document"&&docType==="acte_authentique"&&(
                        <button onClick={()=>openArchiveInline(notif.id,action,"archive_document")}
                          style={btnS("#7c3aed",true)}>✓ Archiver l'acte</button>
                      )}
                      {!canExecute&&notif.category==="eg_appel_fonds"&&(
                        <button onClick={()=>setConvertirModal({notif})}
                          style={btnS("#b45309",true)}>💶 Convertir en appel de fonds</button>
                      )}
                      {!canExecute&&notif.category==="architecte_attestation"&&(
                        <button onClick={()=>setAttestContextModal({notif})}
                          style={btnS("#0369a1",true)}>💶 Générer appel client</button>
                      )}
                      {!canExecute&&notif.category!=="eg_appel_fonds"&&notif.category!=="architecte_attestation"&&<button onClick={validate} style={btnS("#16a34a",true)}>✓ Valider</button>}
                      {!canExecute&&(notif.category==="eg_appel_fonds"||notif.category==="architecte_attestation")&&<button onClick={validate} style={btnS("#16a34a",true)}>✓ Traité</button>}
                      {!isEditing&&<button onClick={dismiss} style={btnS("#64748b",true)}>✕ Écarter</button>}
                      <button
                        onClick={()=>reclassify(notif.id)}
                        disabled={!!reclassifyingIds[notif.id]}
                        title="Relancer la classification automatique sur cet email uniquement"
                        style={{background:"none",border:"1px solid #e2e8f0",borderRadius:6,padding:"4px 10px",fontSize:11,cursor:reclassifyingIds[notif.id]?"default":"pointer",color:"#64748b"}}>
                        {reclassifyingIds[notif.id]?"⏳":"🔄"} Re-classifier
                      </button>
                    </div>

                    {/* Ligne 2 : forçage manuel — séparée visuellement */}
                    <div style={{display:"flex",gap:6,alignItems:"center",flexWrap:"wrap",marginTop:8,paddingTop:8,borderTop:"1px dashed #e2e8f0"}}>
                      <span style={{fontSize:10,color:"#94a3b8",fontWeight:600,whiteSpace:"nowrap"}}>Forcer la catégorie :</span>
                      <select
                        value={forceCatState[notif.id]||""}
                        onChange={e=>setForceCatState(s=>({...s,[notif.id]:e.target.value}))}
                        style={{background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:5,fontSize:11,padding:"3px 6px",color:"#1e293b"}}>
                        <option value="">— choisir —</option>
                        {ALL_CATS.map(c=><option key={c} value={c}>{c}</option>)}
                      </select>
                      <button
                        onClick={()=>forceCategory(notif.id)}
                        disabled={!forceCatState[notif.id]||!!forcingIds[notif.id]}
                        style={{background:!forceCatState[notif.id]||forcingIds[notif.id]?"#94a3b8":"#7c3aed",color:"#fff",border:"none",borderRadius:5,padding:"3px 10px",fontSize:11,cursor:!forceCatState[notif.id]||forcingIds[notif.id]?"default":"pointer",fontWeight:600}}>
                        {forcingIds[notif.id]?"⏳":"✓"} Forcer
                      </button>
                    </div>

                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}

      {facturesVisible.length===0&&autresVisible.length===0&&(
        <div style={{background:C.card,borderRadius:12,border:"1px dashed "+C.border,padding:32,textAlign:"center"}}>
          <div style={{fontSize:40,marginBottom:12}}>🔕</div>
          <div style={{fontWeight:700,fontSize:15,marginBottom:6}}>Aucune action en attente</div>
          <div style={{fontSize:12,color:C.muted}}>Toutes les notifications ont été traitées.</div>
        </div>
      )}

      {/* ── Purge ── */}
      <div style={{marginTop:16,textAlign:"right"}}>
        <button onClick={async()=>{
          if(!confirm("Supprimer toutes les notifications traitées ou refusées de plus de 12 mois ?")) return;
          try{
            const r=await fetch("/api/agent/notifications/purge",{method:"POST",headers:{"Content-Type":"application/json"}});
            const d=await r.json();
            if(d.error){alert("Erreur : "+d.error);return;}
            if(d.deleted===0) alert("Aucune notification à purger (critères : traitées/refusées et antérieures à 12 mois).");
            else{alert(d.deleted+" notification(s) supprimée(s).");loadNotifs(notifShowAll);}
          }catch(e){alert("Erreur réseau : "+e.message);}
        }} style={{background:"none",border:"1px solid #e2e8f0",borderRadius:7,padding:"5px 14px",
          fontSize:11,cursor:"pointer",color:"#94a3b8",fontWeight:600}}>
          🗑 Purger les anciennes (&gt;12 mois)
        </button>
      </div>
    </>
    )}

    {/* ── Modale prévisualisation PDF ── */}
    {notifPdfModal&&(
      <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.75)",zIndex:3000,display:"flex",flexDirection:"column"}}
        onClick={e=>{if(e.target===e.currentTarget)setNotifPdfModal(null);}}>
        <div style={{display:"flex",justifyContent:"flex-end",padding:"8px 16px",flexShrink:0}}>
          <button onClick={()=>setNotifPdfModal(null)} style={{background:"#fff",border:"none",borderRadius:6,padding:"5px 16px",fontWeight:700,cursor:"pointer",fontSize:14}}>✕ Fermer</button>
        </div>
        <iframe src={"/api/agent/notifications/"+notifPdfModal+"/attachment"} style={{flex:1,border:"none",background:"#fff"}} title="Prévisualisation PDF"/>
      </div>
    )}

    {/* ── Modale Régler facture ── */}
    {reglerModal&&(
      <FactureReglerModal
        notifId={reglerModal.id}
        factureData={reglerModal.facture_data}
        proposedAction={reglerModal.proposed_action}
        projects={projects}
        rows={rows}
        fiche={fiches}
        allValues={values}
        onSave={(rowId,weekIso,montant,redist)=>{ handleCellSave(rowId,weekIso,montant,redist,true); }}
        onClose={()=>{ setReglerModal(null); loadNotifs(notifShowAll); }}
      />
    )}

    {/* ── Modale Convertir en appel de fonds ── */}
    {convertirModal&&(
      <ConvertirAppelFondsModal
        notif={convertirModal.notif}
        projects={projects}
        fiches={fiches}
        crm={crm}
        onClose={()=>setConvertirModal(null)}
        onDone={async()=>{
          await fetch("/api/agent/notifications/"+convertirModal.notif.id,{
            method:"PATCH",headers:{"Content-Type":"application/json"},
            body:JSON.stringify({action:"validate"})
          }).catch(()=>{});
          setConvertirModal(null);
          loadNotifs(notifShowAll);
        }}
      />
    )}

    {/* ── Modale contexte attestation (étape 0 : lecture PDF Haiku) ── */}
    {attestContextModal&&(
      <AttestationContextModal
        notif={attestContextModal.notif}
        projects={projects}
        fiches={fiches}
        onConfirm={(enrichedNotif)=>{
          setAttestContextModal(null);
          setAttestArchiveModal({notif:enrichedNotif});
        }}
        onCancel={()=>setAttestContextModal(null)}
      />
    )}

    {/* ── Modale archivage attestation (étape 1 avant AppelClientEgModal) ── */}
    {attestArchiveModal&&(
      <AttestationArchiveModal
        notif={attestArchiveModal.notif}
        onArchived={(notif,shareUrl,folderPath)=>{
          setAttestArchiveModal(null);
          setAppelClientModal({notif,attestFolderPath:folderPath||null});
        }}
        onCancel={()=>setAttestArchiveModal(null)}
      />
    )}

    {/* ── Modale Générer appel client (architecte_attestation) ── */}
    {appelClientModal&&(
      <AppelClientEgModal
        notif={appelClientModal.notif}
        attestFolderPath={appelClientModal.attestFolderPath||null}
        projects={projects}
        fiches={fiches}
        crm={crm}
        rows={rows}
        values={values}
        onClose={()=>setAppelClientModal(null)}
        onDone={async()=>{
          await fetch("/api/agent/notifications/"+appelClientModal.notif.id,{
            method:"PATCH",headers:{"Content-Type":"application/json"},
            body:JSON.stringify({action:"validate"})
          }).catch(()=>{});
          setAppelClientModal(null);
          loadNotifs(notifShowAll);
        }}
      />
    )}

    {/* ── Modale LIA incomplète ── */}
    {incompleteLiaModal&&(
      <IncompleteLiaModal
        state={incompleteLiaModal}
        onSent={async()=>{
          await fetch("/api/agent/notifications/"+incompleteLiaModal.notifId,{
            method:"PATCH",headers:{"Content-Type":"application/json"},
            body:JSON.stringify({action:"mark_done"})
          }).catch(()=>{});
          setIncompleteLiaModal(null);
          loadNotifs(notifShowAll);
        }}
        onClose={()=>setIncompleteLiaModal(null)}
      />
    )}

  </div>
  );
}

/* ── Modale : Contexte attestation architecte (étape 0 — lecture PDF Haiku) ─── */
function AttestationContextModal({notif,projects,fiches,onConfirm,onCancel}){
  const inp={background:'#f8fafc',border:'1px solid #cbd5e1',borderRadius:6,padding:'7px 10px',fontSize:13,color:'#1e293b',width:'100%',boxSizing:'border-box'};
  const btn=(bg,sm)=>({background:bg||'#2563eb',color:bg==='#e2e8f0'?'#475569':'#fff',border:'none',borderRadius:6,padding:sm?'5px 12px':'8px 18px',cursor:'pointer',fontSize:sm?12:13,fontWeight:600,whiteSpace:'nowrap'});

  const metaRaw=notif.metadata||{};
  const [loading,  setLoading]  =React.useState(true);
  const [error,    setError]    =React.useState(null);
  const [projId,   setProjId]   =React.useState(String(metaRaw.programme_id||''));
  const [lotIdx,   setLotIdx]   =React.useState(metaRaw.lot_idx!=null?String(metaRaw.lot_idx):'');
  const [pctTotal, setPctTotal] =React.useState(metaRaw.avancement_pct!=null?String(metaRaw.avancement_pct):'');
  const [pctAppele,setPctAppele]=React.useState('');
  const [pctCumule,setPctCumule]=React.useState(0);

  // Lecture du PDF via Haiku au montage
  React.useEffect(()=>{
    (async()=>{
      try{
        const r=await fetch('/api/agent/notifications/'+notif.id+'/read-attestation',{method:'POST'});
        const d=await r.json();
        if(d.ok){
          if(d.programme_id!=null) setProjId(String(d.programme_id));
          if(d.lot_idx!=null)      setLotIdx(String(d.lot_idx));
          if(d.avancement_pct!=null) setPctTotal(String(d.avancement_pct));
          if(d.pct_cumule!=null)   setPctCumule(Number(d.pct_cumule));
        } else {
          setError(d.error||'Erreur inconnue');
        }
      }catch(e){ setError('Erreur réseau : '+e.message); }
      finally{ setLoading(false); }
    })();
  },[]);

  // Recalcul automatique du % appelé quand pctTotal ou pctCumule change
  React.useEffect(()=>{
    const t=parseInt(pctTotal,10);
    if(!isNaN(t)) setPctAppele(String(Math.max(0,t-pctCumule)));
  },[pctTotal,pctCumule]);

  const lots=projId?((fiches[projId]||{}).lots||[]):[];
  const pctTotalNum =parseInt(pctTotal,10);
  const pctAppeleNum=parseInt(pctAppele,10);
  const pctAttendu  =!isNaN(pctTotalNum)?pctTotalNum-pctCumule:null;
  const showWarn    =!isNaN(pctAppeleNum)&&pctAttendu!=null&&Math.abs(pctAppeleNum-pctAttendu)>0;
  const canConfirm  =projId&&lotIdx!==''&&!isNaN(pctTotalNum)&&pctTotalNum>0;

  const handleConfirm=()=>{
    const prog=(projects||[]).find(p=>String(p.id)===String(projId));
    const seg=s=>(s||'').replace(/[/\\:*?"<>|]/g,'_').trim()||'inconnu';
    const ville=seg(prog?.ville||'');
    const nom  =seg(prog?.nom  ||'');
    const progSeg=ville?`${ville} - ${nom}`:nom;
    const lotN =parseInt(lotIdx,10)+1;
    const pct  =pctTotalNum||'??';
    const suggestedFolder =`\\Blue\\Programmes\\Programmes validés\\${progSeg}\\03 - Vente\\02 - Lots\\Lot ${lotN}\\04 - Appels de fonds\\${pct}%\\`;
    const suggestedFilename=`Attestation_${pct}pct_Lot${lotN}.pdf`;

    const enrichedNotif={
      ...notif,
      metadata:{
        ...(notif.metadata||{}),
        programme_id:projId||null,
        lot_idx:lotIdx!==''?parseInt(lotIdx,10):null,
        avancement_pct:pctTotalNum||null,
      },
      proposed_action:{
        ...(notif.proposed_action||{}),
        params:{
          ...((notif.proposed_action||{}).params||{}),
          programmeId:projId||null,
          lotIdx:lotIdx!==''?parseInt(lotIdx,10):null,
          avancement_pct:pctTotalNum||null,
          pct_appele:!isNaN(pctAppeleNum)?pctAppeleNum:null,
          suggestedFolder,
          suggestedFilename,
        },
      },
    };
    onConfirm(enrichedNotif);
  };

  return(
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.5)',zIndex:3000,display:'flex',alignItems:'center',justifyContent:'center',padding:16}}>
      <div style={{background:'#fff',borderRadius:12,padding:28,maxWidth:500,width:'100%',maxHeight:'85vh',overflowY:'auto',boxShadow:'0 20px 60px rgba(0,0,0,0.25)'}}>

        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>📋 Contexte de l'attestation</div>
        <div style={{fontSize:12,color:'#64748b',marginBottom:20}}>
          {loading?'Lecture du PDF en cours…':'Vérifiez et complétez les informations extraites du document.'}
        </div>

        {loading&&(
          <div style={{textAlign:'center',padding:'32px 0',color:'#64748b',fontSize:14}}>⏳ Haiku lit l'attestation…</div>
        )}

        {!loading&&(<>
          {error&&(
            <div style={{background:'#fee2e2',borderRadius:7,padding:'8px 12px',fontSize:12,color:'#dc2626',marginBottom:14}}>
              ⚠️ {error} — vous pouvez remplir le formulaire manuellement.
            </div>
          )}

          {/* Programme */}
          <div style={{marginBottom:14}}>
            <label style={{fontSize:12,fontWeight:600,color:'#475569',display:'block',marginBottom:4}}>Programme</label>
            <select value={projId} onChange={e=>{setProjId(e.target.value);setLotIdx('');}} style={inp}>
              <option value="">— sélectionner —</option>
              {(projects||[]).map(p=>(
                <option key={p.id} value={String(p.id)}>{p.ville?p.ville+' - ':''}{p.nom}</option>
              ))}
            </select>
          </div>

          {/* Lot */}
          <div style={{marginBottom:14}}>
            <label style={{fontSize:12,fontWeight:600,color:'#475569',display:'block',marginBottom:4}}>Lot</label>
            <select value={lotIdx} onChange={e=>setLotIdx(e.target.value)} style={inp} disabled={!projId}>
              <option value="">— sélectionner —</option>
              {lots.map((l,i)=>(
                <option key={i} value={String(i)}>Lot {i+1}{l.clientNom?' — '+l.clientNom:''}</option>
              ))}
            </select>
          </div>

          {/* % avancement total */}
          <div style={{marginBottom:14}}>
            <label style={{fontSize:12,fontWeight:600,color:'#475569',display:'block',marginBottom:4}}>% avancement total (cumulé après cet appel)</label>
            <div style={{display:'flex',alignItems:'center',gap:8}}>
              <input type="number" min="1" max="100" value={pctTotal} onChange={e=>setPctTotal(e.target.value)} style={{...inp,width:90}} placeholder="ex : 20"/>
              <span style={{fontSize:12,color:'#64748b'}}>%</span>
              {pctCumule>0&&<span style={{fontSize:11,color:'#94a3b8'}}>Cumul actuel : {pctCumule}%</span>}
            </div>
          </div>

          {/* % appelé (cet appel) */}
          <div style={{marginBottom:16}}>
            <label style={{fontSize:12,fontWeight:600,color:'#475569',display:'block',marginBottom:4}}>% travaux appelé (cet appel uniquement)</label>
            <div style={{display:'flex',alignItems:'center',gap:8}}>
              <input type="number" min="0" max="100" value={pctAppele} onChange={e=>setPctAppele(e.target.value)} style={{...inp,width:90}}/>
              <span style={{fontSize:12,color:'#64748b'}}>%</span>
            </div>
          </div>

          {/* Warning écart */}
          {showWarn&&(
            <div style={{background:'#fff7ed',border:'1px solid #fed7aa',borderRadius:7,padding:'8px 12px',fontSize:12,color:'#c2410c',marginBottom:14}}>
              ⚠️ Le % appelé ({pctAppele}%) ne correspond pas à l'avancement total ({pctTotal}%) moins le cumul déjà appelé ({pctCumule}%). Écart : {Math.abs(pctAppeleNum-pctAttendu)}%.
            </div>
          )}

          <div style={{display:'flex',gap:8,justifyContent:'flex-end',marginTop:4}}>
            <button onClick={onCancel} style={btn('#e2e8f0',false)}>Annuler</button>
            <button onClick={handleConfirm} disabled={!canConfirm}
              style={{...btn(canConfirm?'#2563eb':'#94a3b8',false),cursor:canConfirm?'pointer':'default'}}>
              Continuer →
            </button>
          </div>
        </>)}
      </div>
    </div>
  );
}

/* ── Modale : Archivage attestation architecte (étape 1) ────────────────────── */
function AttestationArchiveModal({notif,onArchived,onCancel}){
  const params=(notif.proposed_action&&notif.proposed_action.params)||{};
  const initFolder  =params.suggestedFolder  ||'\\Blue\\Programmes\\Programmes validés';
  const initFilename=params.suggestedFilename||'Attestation_avancement.pdf';

  const [currentPath,setCurrentPath]=React.useState('');
  const [folders,    setFolders]    =React.useState([]);
  const [pathExists, setPathExists] =React.useState(true);
  const [filename,   setFilename]   =React.useState(initFilename);
  const [newName,    setNewName]    =React.useState('');
  const [showCreate, setShowCreate] =React.useState(false);
  const [loading,    setLoading]    =React.useState(false);
  const [archiving,  setArchiving]  =React.useState(false);
  const [creating,   setCreating]   =React.useState(false);
  const [error,      setError]      =React.useState(null);

  const inp={background:'#f8fafc',border:'1px solid #cbd5e1',borderRadius:6,padding:'7px 10px',fontSize:13,color:'#1e293b',width:'100%',boxSizing:'border-box'};
  const btn=(bg,sm)=>({background:bg||'#2563eb',color:bg==='#e2e8f0'?'#475569':'#fff',border:'none',borderRadius:6,padding:sm?'5px 12px':'8px 18px',cursor:'pointer',fontSize:sm?12:13,fontWeight:600,whiteSpace:'nowrap'});

  const browseTo=React.useCallback(async(path)=>{
    setLoading(true);setError(null);
    try{
      const r=await fetch('/api/agent/onedrive/browse?path='+encodeURIComponent(path));
      const d=await r.json();
      setCurrentPath(path);setFolders(d.folders||[]);setPathExists(d.exists!==false);
    }catch(e){setError('Impossible de charger le dossier : '+e.message);}
    finally{setLoading(false);}
  },[]);

  // Au montage : essaie le chemin suggéré, puis remonte de niveau en niveau
  React.useEffect(()=>{
    const segs=(initFolder||'').replace(/\\+$/,'').split('\\').filter(Boolean);
    const chain=[
      initFolder,
      segs.length>1?'\\'+segs.slice(0,-1).join('\\'):null,
      '\\Blue\\Programmes\\Programmes validés',
    ].filter(Boolean);

    (async()=>{
      setLoading(true);
      for(const path of chain){
        try{
          const r=await fetch('/api/agent/onedrive/browse?path='+encodeURIComponent(path));
          const d=await r.json();
          if(d.exists!==false){
            setCurrentPath(path);setFolders(d.folders||[]);setPathExists(true);setLoading(false);
            return;
          }
        }catch(_){}
      }
      // dernier recours : on affiche quand même le dernier chemin
      browseTo('\\Blue\\Programmes\\Programmes validés');
    })();
  },[]);

  const goUp=()=>{
    const segs=currentPath.replace(/\\+$/,'').split('\\').filter(Boolean);
    if(segs.length<=1)return;
    browseTo('\\'+segs.slice(0,-1).join('\\'));
  };

  const createFolder=async()=>{
    if(!newName.trim())return;
    setCreating(true);setError(null);
    try{
      const full=currentPath.replace(/\\+$/,'')+'\\'+newName.trim();
      const r=await fetch('/api/agent/onedrive/mkdir',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:full})});
      const d=await r.json();
      if(!d.ok)throw new Error(d.error||'Erreur création dossier');
      setNewName('');setShowCreate(false);
      await browseTo(full);
    }catch(e){setError('Impossible de créer le dossier : '+e.message);}
    finally{setCreating(false);}
  };

  const archive=async()=>{
    if(!filename.trim()||!currentPath)return;
    setArchiving(true);setError(null);
    try{
      const r=await fetch('/api/agent/notifications/'+notif.id+'/archive-attachment',{
        method:'POST',headers:{'Content-Type':'application/json'},
        body:JSON.stringify({folderPath:currentPath,filename:filename.trim()})
      });
      const d=await r.json();
      if(!d.ok)throw new Error(d.error||'Erreur archivage');
      onArchived(notif,d.shareUrl,currentPath);
    }catch(e){setError('Archivage échoué : '+e.message);}
    finally{setArchiving(false);}
  };

  // Breadcrumb : segments du chemin courant
  const crumbs=currentPath?currentPath.replace(/\\+$/,'').split('\\').filter(Boolean):[];

  return(
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.5)',zIndex:3000,display:'flex',alignItems:'center',justifyContent:'center',padding:16}}>
      <div style={{background:'#fff',borderRadius:12,padding:28,maxWidth:600,width:'100%',maxHeight:'85vh',overflowY:'auto',boxShadow:'0 20px 60px rgba(0,0,0,0.25)'}}>

        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>📁 Archiver l'attestation architecte</div>
        <div style={{fontSize:12,color:'#64748b',marginBottom:20}}>Sélectionnez le dossier de destination, puis cliquez sur Archiver pour continuer vers la génération de l'appel client.</div>

        {/* Nom de fichier */}
        <div style={{marginBottom:16}}>
          <label style={{fontSize:12,fontWeight:600,color:'#475569',display:'block',marginBottom:4}}>Nom du fichier</label>
          <input value={filename} onChange={e=>setFilename(e.target.value)} style={inp} placeholder="Attestation_10pct_Lot1.pdf"/>
        </div>

        {/* Navigateur dossiers */}
        <div style={{marginBottom:12}}>
          <label style={{fontSize:12,fontWeight:600,color:'#475569',display:'block',marginBottom:6}}>Dossier de destination</label>

          {/* Breadcrumb */}
          <div style={{background:'#f1f5f9',borderRadius:6,padding:'6px 10px',fontSize:11,color:'#475569',marginBottom:8,display:'flex',alignItems:'center',gap:4,flexWrap:'wrap',minHeight:30}}>
            {crumbs.length>0
              ?crumbs.map((seg,i)=>(
                <React.Fragment key={i}>
                  {i>0&&<span style={{color:'#94a3b8',userSelect:'none'}}>\</span>}
                  <span
                    onClick={()=>{ if(i<crumbs.length-1) browseTo('\\'+crumbs.slice(0,i+1).join('\\')); }}
                    style={{cursor:i<crumbs.length-1?'pointer':'default',color:i<crumbs.length-1?'#2563eb':'#1e293b',fontWeight:i===crumbs.length-1?600:400}}
                  >{seg}</span>
                </React.Fragment>
              ))
              :<span style={{color:'#94a3b8'}}>Chargement…</span>
            }
          </div>

          {/* Bouton remonter */}
          {crumbs.length>1&&!loading&&(
            <button onClick={goUp} style={{...btn('#64748b',true),marginBottom:8}}>↑ Remonter</button>
          )}

          {/* Liste sous-dossiers */}
          <div style={{border:'1px solid #e2e8f0',borderRadius:8,overflow:'hidden',marginBottom:8,maxHeight:220,overflowY:'auto'}}>
            {loading
              ?<div style={{padding:'14px 12px',fontSize:12,color:'#94a3b8',textAlign:'center'}}>Chargement…</div>
              :folders.length===0
                ?<div style={{padding:'14px 12px',fontSize:12,color:'#94a3b8',textAlign:'center'}}>Aucun sous-dossier</div>
                :folders.map((f,i)=>(
                  <div key={i}
                    onClick={()=>browseTo(f.path)}
                    style={{padding:'9px 14px',fontSize:13,cursor:'pointer',borderBottom:i<folders.length-1?'1px solid #f1f5f9':'none',display:'flex',alignItems:'center',gap:8,background:'#fff'}}
                    onMouseEnter={e=>e.currentTarget.style.background='#f8fafc'}
                    onMouseLeave={e=>e.currentTarget.style.background='#fff'}
                  >
                    <span>📁</span><span>{f.name}</span>
                  </div>
                ))
            }
          </div>

          {/* Création dossier */}
          {!showCreate
            ?<button onClick={()=>setShowCreate(true)} style={btn('#e2e8f0',true)}>+ Créer un dossier ici</button>
            :<div style={{display:'flex',gap:6,alignItems:'center'}}>
              <input
                value={newName} onChange={e=>setNewName(e.target.value)}
                onKeyDown={e=>{if(e.key==='Enter')createFolder();if(e.key==='Escape'){setShowCreate(false);setNewName('');}}}
                placeholder="Nom du dossier (ex : 10%)"
                style={{...inp,flex:1}} autoFocus
              />
              <button onClick={createFolder} disabled={creating||!newName.trim()} style={btn(creating||!newName.trim()?'#94a3b8':'#2563eb',true)}>
                {creating?'⏳':'Créer'}
              </button>
              <button onClick={()=>{setShowCreate(false);setNewName('');}} style={btn('#94a3b8',true)}>✕</button>
            </div>
          }
        </div>

        {error&&<div style={{background:'#fee2e2',border:'1px solid #fca5a5',borderRadius:6,padding:'8px 12px',fontSize:12,color:'#dc2626',marginBottom:12}}>⚠️ {error}</div>}

        {/* Résumé destination */}
        {currentPath&&filename&&(
          <div style={{background:'#f0fdf4',border:'1px solid #86efac',borderRadius:6,padding:'8px 12px',fontSize:11,color:'#15803d',marginBottom:16,wordBreak:'break-all'}}>
            📄 <strong>{currentPath.replace(/\\+$/,'')}\\{filename}</strong>
          </div>
        )}

        {/* Boutons */}
        <div style={{display:'flex',gap:8,justifyContent:'flex-end',marginTop:8}}>
          <button onClick={onCancel} disabled={archiving} style={btn('#64748b')}>Annuler</button>
          <button
            onClick={archive}
            disabled={archiving||!filename.trim()||!currentPath||loading}
            style={btn(archiving||!filename.trim()||!currentPath||loading?'#94a3b8':'#0369a1')}>
            {archiving?'⏳ Archivage…':'📁 Archiver & continuer →'}
          </button>
        </div>

      </div>
    </div>
  );
}

/* ── Modale : Convertir notif EG en appel de fonds (2 étapes) ───────────────── */
function ConvertirAppelFondsModal({notif,projects,fiches,crm,onClose,onDone}) {
  const meta=(notif.metadata)||{};
  const action=(notif.proposed_action)||{};

  // ── Parsing du nom de PJ pour auto-remplissage ───────────────────────────
  // Ex : "Situation Arles 389 - Arles 20% lot4.pdf" → pct=20, lotNum=4, projId=…
  let _filePct=null, _fileLotNum=null, _fileProjId="";
  try{
    const _ref=JSON.parse(notif.attachment_ref||'{}');
    const _fn=(_ref.filename||notif.subject||'');
    const _pm=_fn.match(/(\d+(?:\.\d+)?)\s*%/);
    const _lm=_fn.match(/lot\s*(\d+)/i);
    if(_pm) _filePct=_pm[1];
    if(_lm) _fileLotNum=parseInt(_lm[1],10);
    // Fuzzy match programme : on cherche un programme dont la ville/nom est dans le nom de fichier
    const _fnLow=_fn.toLowerCase();
    const _activeProjs=(projects||[]).filter(p=>!p.isGlobal&&(!p.statut||p.statut==='En cours'));
    for(const _p of _activeProjs){
      const _ville=(_p.ville||'').toLowerCase();
      const _nom=(_p.nom||'').toLowerCase();
      if(_ville.length>2&&_fnLow.includes(_ville)){_fileProjId=_p.id;break;}
      if(_nom.length>2&&_fnLow.includes(_nom)){_fileProjId=_p.id;break;}
    }
  }catch(_){}

  // ── Étape et appel créé ──────────────────────────────────────────────────
  const [step,setStep]=React.useState(1);
  const [createdAppel,setCreatedAppel]=React.useState(null);

  // ── Step 1 : saisie ──────────────────────────────────────────────────────
  const preProjId=meta.programme_id||action?.params?.programmeId||_fileProjId||"";
  const [projId,setProjId]=React.useState(preProjId);
  const lots=React.useMemo(()=>{
    const fiche=fiches[projId];
    if(!fiche||!fiche.lots) return [];
    return fiche.lots.map((l,i)=>({idx:i,lot:l})).filter(({lot})=>
      ["VENDU","AV10","AV40","AV70","AV95","LIVRE"].includes(lot.statutCommercial||"")
    );
  },[projId,fiches]);
  const [lotIdx,setLotIdx]=React.useState("");
  const [egPct,setEgPct]=React.useState(String(meta.eg_pct||meta.pct||meta.avancement_pct||_filePct||""));
  const [egMontant,setEgMontant]=React.useState(String(meta.eg_montant||meta.montant||meta.montant_ttc||""));
  const [saving,setSaving]=React.useState(false);
  const [err,setErr]=React.useState("");


  // Helper : calcule travauxReel depuis les champs bruts de la fiche (même formule que FicheDetail)
  const calcTravauxReel=React.useCallback((pId,lIdx)=>{
    try{
      const f=(fiches||{})[pId]||{};
      const lot=((f.lots)||[])[parseInt(lIdx,10)]||{};
      const toN=v=>parseFloat(String(v||'').replace(/\s/g,'').replace(',','.'))||0;
      const prixAchat=toN(f.prixAchat), devisTravaux=toN(f.devisTravaux);
      const euribor=toN(f.euribor), montantCredit=toN(f.montantCredit);
      const creditMensuel=montantCredit*(euribor/100+0.025)/12;
      const montantCommerce=toN(f.montantCommerceRdc);
      const prixDenormandie=prixAchat-montantCommerce;
      const creditTotal=creditMensuel*8;
      const achatFNI=prixDenormandie*1.025+toN(f.dossierBanque)+creditTotal;
      const moe=devisTravaux*0.08;
      const montantTravaux=moe+toN(f.dp)+toN(f.pc)+toN(f.plaquette)+toN(f.deplacements)
        +toN(f.avocat)+toN(f.hommeArt)+toN(f.geometre)+toN(f.diagnostics)
        +toN(f.loyerMeuble)+toN(f.doTrc)+toN(f.gfa)+devisTravaux;
      const prixTotalHorsMarge=achatFNI+montantTravaux;
      const prixReelVal=toN(lot.prixReel);
      if(prixReelVal<=0||prixTotalHorsMarge<=0) return 0;
      const foncierReelCalc=prixReelVal*achatFNI/prixTotalHorsMarge;
      const foncierReelOv=lot.foncierReelO!==""&&lot.foncierReelO!=null?toN(lot.foncierReelO):null;
      const foncierReel=foncierReelOv!==null?foncierReelOv:foncierReelCalc;
      return prixReelVal-foncierReel;
    }catch(_){return 0;}
  },[fiches]);

  // ── Step 2 : email architecte ─────────────────────────────────────────────
  const [archSending,setArchSending]=React.useState(false);
  const [archSent,setArchSent]=React.useState(false);
  const [archErr,setArchErr]=React.useState("");
  const [egAttachment,setEgAttachment]=React.useState(null);
  const [loadingAttach,setLoadingAttach]=React.useState(false);
  const [factureNotifId,setFactureNotifId]=React.useState(null);

  // ── Auto-sélection du lot si numéro parsé depuis la PJ ──────────────────
  React.useEffect(()=>{
    if(!projId||_fileLotNum==null||lotIdx!=="") return;
    const targetIdx=_fileLotNum-1; // le nom de fichier est 1-based (lot4 → idx 3)
    if(lots.find(l=>l.idx===targetIdx)) setLotIdx(String(targetIdx));
  },[projId,lots]);

  // Architecte depuis CRM (entreprise activite=Architecte ou nom Karoubi)
  const architecte=React.useMemo(()=>{
    try{
      if(!crm) return null;
      for(const e of(crm.entreprises||[])){
        if((e.activite||'').toLowerCase()==='architecte'||(e.nom||'').toLowerCase().includes('karoubi')){
          const email=String(e.emails||e.email||'').split(/[,;\s]/)[0].trim();
          return {nom:e.nom,email};
        }
        for(const c of(e.contacts||[])){
          if((c.nom||'').toLowerCase().includes('karoubi')||(c.prenom||'').toLowerCase().includes('yael')){
            return {nom:`${c.prenom||''} ${c.nom||''}`.trim(),email:c.email||''};
          }
        }
      }
      return null;
    }catch(e){
      console.error('[ConvertirModal] architecte useMemo error:',e);
      return null;
    }
  },[crm]);

  // Corps email architecte (recalculé quand step=2)
  const emailArchBody=React.useMemo(()=>{
    try{
      if(step!==2||!createdAppel) return '';
      const lotInfo=lots.find(l=>l.idx===parseInt(lotIdx,10));
      const lot=lotInfo?.lot||{};
      const fiche=(fiches||{})[projId]||{};
      const proj=((projects)||[]).find(p=>p.id===projId)||{};
      const progNom=[proj.ville,proj.nom].filter(Boolean).join(' ')||fiche.adresse||'';
      const clientNom=lot.clientNom||'[Client]';
      const archNom=architecte?.nom||'Madame Karoubi';
      const pct=Number(createdAppel.eg_pct||0);
      const montant=createdAppel.eg_montant?Number(createdAppel.eg_montant).toLocaleString('fr-FR',{minimumFractionDigits:2,maximumFractionDigits:2}):'';
      const lotN=parseInt(lotIdx,10)+1;
      const villeRef=String(proj.ville||proj.nom||'PROG').replace(/\s+/g,'-').toUpperCase().slice(0,15);
      const ref=`LOT${lotN}-${villeRef}-AV${pct}`;
      return `Objet : Demande attestation avancement ${pct}% — ${progNom} — Lot ${lotN} [Réf: ${ref}]

${archNom},

Dans le cadre du programme ${progNom}, nous avons reçu la facture de l'entreprise générale correspondant à un avancement de ${pct}%${montant?` (${montant} €)`:''}. Vous trouverez cette facture en pièce jointe.

Nous vous prions de bien vouloir nous faire parvenir votre attestation d'avancement des travaux correspondante afin de procéder à l'appel de fonds client.

INFORMATIONS :
─────────────────────────────────────────
  Programme   : ${progNom}
  Lot N°      : ${lotN}${lot.type?` (${lot.type})`:''}
  Acquéreur   : ${clientNom}
  Avancement  : ${pct}%
─────────────────────────────────────────

Nous vous remercions de votre diligence.

Cordialement,`;
    }catch(e){
      console.error('[ConvertirModal] emailArchBody error:',e);
      return '(Erreur lors de la génération du corps — voir console navigateur)';
    }
  },[step,createdAppel,projId,lotIdx,fiches,projects,architecte,lots]);

  // Charge la PJ EG (PDF) depuis la notification source
  const loadEgAttachment=React.useCallback(async()=>{
    setLoadingAttach(true);
    try{
      const resp=await fetch(`/api/agent/notifications/${notif.id}/attachment`);
      if(!resp.ok){setLoadingAttach(false);return;}
      const blob=await resp.blob();
      const base64=await new Promise(resolve=>{
        const reader=new FileReader();
        reader.onloadend=()=>resolve(reader.result.split(',')[1]);
        reader.readAsDataURL(blob);
      });
      let filename='facture_eg.pdf';
      try{const ref=JSON.parse(notif.attachment_ref||'{}');filename=ref.filename||filename;}catch(_){}
      setEgAttachment({filename,contentType:'application/pdf',content:base64});
    }catch(e){console.warn('Chargement PJ EG échoué:',e.message);}
    setLoadingAttach(false);
  },[notif.id,notif.attachment_ref]);

  const inp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:6,color:"#1e293b",padding:"6px 8px",fontSize:13,width:"100%"};

  // ── Handler Step 1 : créer l'appel ──────────────────────────────────────
  const handleSubmit=async()=>{
    if(!projId){setErr("Veuillez sélectionner un programme.");return;}
    if(lotIdx===""){setErr("Veuillez sélectionner un lot.");return;}
    if(!lots.find(l=>String(l.idx)===lotIdx)){setErr("Lot invalide ou non vendu pour ce programme.");return;}
    const pct=parseFloat(egPct);
    if(isNaN(pct)||pct<=0){setErr("Le % d'avancement doit être renseigné.");return;}
    setSaving(true); setErr("");
    try{
      const body={
        lot_id:       String(Number(lotIdx)+1),
        programme_id: projId,
        eg_notif_id:  notif.id,
        eg_date:      new Date().toISOString().slice(0,10),
        eg_pct:       pct,
        eg_montant:   parseFloat(egMontant)||null,
        eg_niveau:    "lot",
        pct_delta:    pct,
      };
      const r=await fetch("/api/appels-eg",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
      const d=await r.json();
      if(!r.ok) throw new Error(d.error||"Erreur serveur");
      setCreatedAppel(d);
      setFactureNotifId(d.facture_notif_id||null);
      setStep(2);
      loadEgAttachment();
    }catch(e){setErr(e.message);}
    setSaving(false);
  };

  // ── Handler Step 2 : envoyer email architecte ───────────────────────────
  const handleSendArch=async()=>{
    const archEmail=architecte?.email||'';
    if(!archEmail){setArchErr("Email de l'architecte introuvable dans le CRM.");return;}
    setArchSending(true); setArchErr("");
    try{
      const lotInfo=lots.find(l=>l.idx===parseInt(lotIdx));
      const lot=lotInfo?.lot||{};
      const fiche=fiches[projId]||{};
      const proj=(projects||[]).find(p=>p.id===projId)||{};
      const progNom=[proj.ville,proj.nom].filter(Boolean).join(' ')||fiche.adresse||'';
      const pct=createdAppel.eg_pct||0;
      const lotN=parseInt(lotIdx)+1;
      const payload={
        to:archEmail,
        subject:`Demande attestation ${pct}% — ${progNom} — Lot ${lotN}`,
        body:emailArchBody,
      };
      if(egAttachment) payload.attachment={filename:egAttachment.filename,contentType:egAttachment.contentType,content:egAttachment.content};
      const r=await fetch("/api/email/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)});
      const d=await r.json();
      if(!r.ok||d.error) throw new Error(d.error||"Erreur envoi");
      // Marquer arch_email_sent
      await fetch(`/api/appels-eg/${createdAppel.id}`,{
        method:"PATCH",headers:{"Content-Type":"application/json"},
        body:JSON.stringify({arch_email_sent:true,arch_email_date:new Date().toISOString()}),
      });
      setArchSent(true);
    }catch(e){setArchErr(e.message);}
    setArchSending(false);
  };

  const projs=(projects||[]).filter(p=>!p.isGlobal&&(!p.statut||p.statut==='En cours'));

  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:3000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}
      onClick={e=>{if(e.target===e.currentTarget)onClose();}}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:540,maxWidth:"96vw",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}}
        onClick={e=>e.stopPropagation()}>

        {/* ════ ÉTAPE 1 : Créer l'appel ════ */}
        {step===1&&<>
          <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>💶 Convertir en appel de fonds</div>
          <div style={{fontSize:12,color:"#64748b",marginBottom:16}}>Notif : <b>{notif.subject}</b></div>

          {(meta.eg_pct||meta.eg_montant||meta.montant||meta.avancement_pct||_filePct||_fileLotNum!=null)&&(
            <div style={{background:"#fef3c7",border:"1px solid #fde68a",borderRadius:8,padding:"8px 12px",marginBottom:14,fontSize:12,color:"#92400e"}}>
              <b>Infos détectées :</b>
              {(meta.eg_pct||meta.avancement_pct||_filePct)&&<span style={{marginLeft:8}}>Avancement : <b>{meta.eg_pct||meta.avancement_pct||_filePct}%</b></span>}
              {_fileLotNum!=null&&<span style={{marginLeft:8}}>Lot : <b>Lot {_fileLotNum}</b></span>}
              {(meta.eg_montant||meta.montant||meta.montant_ttc)&&<span style={{marginLeft:8}}>Montant EG : <b>{Number(meta.eg_montant||meta.montant||meta.montant_ttc).toLocaleString("fr-FR")} €</b></span>}
            </div>
          )}

          <div style={{display:"flex",flexDirection:"column",gap:10,marginBottom:16}}>
            <div>
              <div style={{fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Programme *</div>
              <select value={projId} onChange={e=>{setProjId(e.target.value);setLotIdx("");}} style={inp}>
                <option value="">— Sélectionner un programme —</option>
                {projs.map(p=><option key={p.id} value={p.id}>{p.ville?p.ville+" – ":""}{p.nom}</option>)}
              </select>
            </div>
            <div>
              <div style={{fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Lot cible (statut VENDU) *</div>
              <select value={lotIdx} onChange={e=>setLotIdx(e.target.value)} style={inp} disabled={!projId||lots.length===0}>
                <option value="">— Sélectionner un lot —</option>
                {lots.map(({idx,lot})=>(
                  <option key={idx} value={String(idx)}>
                    Lot {idx+1}{lot.type?" ("+lot.type+")":""}{lot.clientNom?" — "+lot.clientNom:""} [{lot.statutCommercial}]
                  </option>
                ))}
              </select>
              {projId&&lots.length===0&&<div style={{fontSize:11,color:"#94a3b8",marginTop:3}}>Aucun lot VENDU dans ce programme.</div>}
            </div>
            <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10}}>
              <div>
                <div style={{fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>% d'avancement (EG) *</div>
                <input type="number" min="0" max="100" step="0.5" value={egPct} onChange={e=>setEgPct(e.target.value)} placeholder="Ex : 10" style={inp}/>
              </div>
              <div>
                <div style={{fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Montant EG (€)</div>
                <input type="number" min="0" step="0.01" value={egMontant} onChange={e=>setEgMontant(e.target.value)} placeholder="Ex : 45000" style={inp}/>
              </div>
            </div>
          </div>

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

          <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
            <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>Annuler</button>
            <button onClick={handleSubmit} disabled={saving}
              style={{background:saving?"#94a3b8":"#b45309",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:saving?"default":"pointer"}}>
              {saving?"⏳ Enregistrement…":"✓ Créer l'appel de fonds"}
            </button>
          </div>
        </>}

        {/* ════ ÉTAPE 2 : Email architecte ════ */}
        {step===2&&<>
          <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>✅ Appel créé — Email architecte</div>
          <div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#15803d"}}>
            ✓ Appel de fonds <b>{createdAppel?.eg_pct}%</b> enregistré. Envoyez la facture EG à l'architecte pour demander son attestation.
          </div>
          {factureNotifId?(
            <div style={{background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,padding:"8px 12px",marginBottom:14,fontSize:12,color:"#1d4ed8"}}>
              🧾 Notification <b>facture</b> créée pour le paiement vers l'EG (n°{factureNotifId}). Elle apparaît dans l'onglet Notifications → Factures, prête à être réglée.
            </div>
          ):(
            <div style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"7px 12px",marginBottom:14,fontSize:11,color:"#64748b"}}>
              ℹ️ Aucune notification facture générée (pas de pièce jointe ou source introuvable).
            </div>
          )}

          {architecte?.email?(
            <div style={{background:"#f0fdf4",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12}}>
              <span style={{color:"#15803d",fontWeight:700}}>Architecte : </span>
              <span style={{fontFamily:"monospace"}}>{architecte.email}</span>
              {architecte.nom&&<span style={{color:"#64748b",marginLeft:8}}>({architecte.nom})</span>}
            </div>
          ):(
            <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12,color:"#92400e"}}>
              ⚠️ Email de l'architecte non trouvé dans le CRM (cherchez "Karoubi" → Entreprises).
            </div>
          )}

          {loadingAttach&&<div style={{fontSize:11,color:"#64748b",marginBottom:8}}>⏳ Chargement de la facture EG…</div>}
          {egAttachment&&!loadingAttach&&(
            <div style={{background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,padding:"6px 12px",marginBottom:10,fontSize:12}}>
              📎 PJ : <b>{egAttachment.filename}</b>
            </div>
          )}
          {!egAttachment&&!loadingAttach&&(
            <div style={{background:"#f1f5f9",borderRadius:8,padding:"6px 12px",marginBottom:10,fontSize:11,color:"#64748b"}}>
              ℹ️ Aucune PJ trouvée sur la notification. L'email sera envoyé sans pièce jointe.
            </div>
          )}

          <div style={{marginBottom:14}}>
            <div style={{fontSize:11,color:"#64748b",marginBottom:4}}>Corps de l'email</div>
            <textarea value={emailArchBody} readOnly
              style={{background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:8,width:"100%",height:220,
                fontFamily:"'Courier New',monospace",fontSize:11,padding:"8px",resize:"vertical",lineHeight:1.5}}/>
          </div>

          {archSent&&(
            <div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"10px 12px",marginBottom:12,fontSize:12,color:"#15803d"}}>
              ✅ Email envoyé. L'agent surveillera la réponse de l'architecte (attestation).
            </div>
          )}
          {archErr&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12,color:"#dc2626"}}>❌ {archErr}</div>}

          <div style={{display:"flex",gap:8,justifyContent:"flex-end",flexWrap:"wrap"}}>
            {!archSent&&<>
              <button onClick={onDone} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>Ignorer l'email</button>
              <button onClick={handleSendArch} disabled={archSending||!architecte?.email}
                style={{background:archSending||!architecte?.email?"#94a3b8":"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:archSending||!architecte?.email?"default":"pointer"}}>
                {archSending?"⏳ Envoi…":"📧 Envoyer à l'architecte"}
              </button>
            </>}
            {archSent&&(
              <button onClick={onDone} style={{background:"#15803d",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:"pointer"}}>
                Fermer
              </button>
            )}
          </div>
        </>}

      </div>
    </div>
  );
}

/* ── Modale : Générer appel de fonds client (depuis attestation architecte) ──── */
function AppelClientEgModal({notif,attestFolderPath,projects,fiches,crm,rows,values,onClose,onDone}) {
  const meta=(notif.metadata)||{};
  const params=(notif.proposed_action&&notif.proposed_action.params)||{};

  // Sélection programme / lot (pré-rempli si metadata, fallback heuristique via params)
  // Sprint F-a : meta.programme_id/lot_idx vient du classifier (whitelist CRM, fiable).
  // params.programmeId/lotIdx vient du résultat enrichi de buildProposedAction()
  // qui ajoute la résolution heuristique (sujet + preview + noms de PJ). On lit
  // metadata d'abord (source de vérité), puis params en fallback (cas où le
  // classifier n'a pas su trouver mais l'heuristique oui).
  const preProjId=meta.programme_id||params.programmeId||"";
  const preLotIdx=meta.lot_idx!=null?String(meta.lot_idx)
                  :params.lotIdx!=null?String(params.lotIdx)
                  :"";
  const [projId,setProjId]=React.useState(preProjId);
  const [lotIdx,setLotIdx]=React.useState(preLotIdx);

  // Appel EG en attente pour ce lot
  const [appel,setAppel]=React.useState(null);
  const [appelLoading,setAppelLoading]=React.useState(false);
  const [appelErr,setAppelErr]=React.useState("");

  // PDF facture client
  const [generating,setGenerating]=React.useState(false);
  // Sprint Indivision-4 : tableau de 1 ou 2 entrées {pdf, filename, recipientEmail?, recipientName?, quotePartPct?, montant?, isPrincipal?}
  // - seul/couple : 1 entrée, sans champs recipientEmail (utilise clientEmail du lot)
  // - indivision : 2 entrées, chacune avec recipientEmail/recipientName/quotePartPct
  const [facturePdfs,setFacturePdfs]=React.useState([]);

  // PJ attestation
  const [attestPdf,setAttestPdf]=React.useState(null);
  const [loadingAttest,setLoadingAttest]=React.useState(false);

  // Email
  const [sending,setSending]=React.useState(false);
  const [sent,setSent]=React.useState(false);
  const [sendErr,setSendErr]=React.useState("");

  // ── Sprint C : modale de replanification (étape 5) ────────────────────────
  const [replanOpen,setReplanOpen]=React.useState(false);

  // ── Cohérence attestation ↔ appel (sprint 2026-05-21) ─────────────────────
  // Si l'avancement certifié dans le PDF (params.avancement_pct) ne correspond
  // pas au pct_delta de l'appel en DB, on affiche un récap et un bouton qui
  // PATCH l'appel (pct_delta + montant_client recalculé) pour réaligner avant
  // génération de la facture.
  const [updatingAppel,setUpdatingAppel]=React.useState(false);
  const [updateAppelErr,setUpdateAppelErr]=React.useState("");

  // ── Création d'un appel ad-hoc (cas historique : appel créé hors système) ─
  // Quand aucun appel en attente n'est trouvé, on propose à l'utilisateur de
  // saisir manuellement les infos pour créer un appel à la volée.
  const [noAppelPending,setNoAppelPending]=React.useState(false);
  const [adhocPct,setAdhocPct]=React.useState(params.pct_appele!=null?String(params.pct_appele):"");
  const [adhocMontant,setAdhocMontant]=React.useState("");
  const [adhocMontantManual,setAdhocMontantManual]=React.useState(false);
  const [adhocAttestPct,setAdhocAttestPct]=React.useState(params.avancement_pct!=null?String(params.avancement_pct):"");
  const [adhocCreating,setAdhocCreating]=React.useState(false);

  // Helper : calcule travauxReel (même formule que FicheDetail)
  const calcTravauxReelClient=React.useCallback((pId,lIdx)=>{
    try{
      const f=(fiches||{})[pId]||{};
      const lot=((f.lots)||[])[parseInt(lIdx,10)]||{};
      const toNl=v=>parseFloat(String(v||'').replace(/\s/g,'').replace(',','.'))||0;
      const prixAchat=toNl(f.prixAchat), devisTravaux=toNl(f.devisTravaux);
      const euribor=toNl(f.euribor), montantCredit=toNl(f.montantCredit);
      const creditMensuel=montantCredit*(euribor/100+0.025)/12;
      const montantCommerce=toNl(f.montantCommerceRdc);
      const prixDenormandie=prixAchat-montantCommerce;
      const creditTotal=creditMensuel*8;
      const achatFNI=prixDenormandie*1.025+toNl(f.dossierBanque)+creditTotal;
      const moe=devisTravaux*0.08;
      const montantTravaux=moe+toNl(f.dp)+toNl(f.pc)+toNl(f.plaquette)+toNl(f.deplacements)
        +toNl(f.avocat)+toNl(f.hommeArt)+toNl(f.geometre)+toNl(f.diagnostics)
        +toNl(f.loyerMeuble)+toNl(f.doTrc)+toNl(f.gfa)+devisTravaux;
      const prixTotalHorsMarge=achatFNI+montantTravaux;
      const prixReelVal=toNl(lot.prixReel);
      if(prixReelVal<=0||prixTotalHorsMarge<=0) return 0;
      const foncierReelCalc=prixReelVal*achatFNI/prixTotalHorsMarge;
      const foncierReelOv=lot.foncierReelO!==""&&lot.foncierReelO!=null?toNl(lot.foncierReelO):null;
      const foncierReel=foncierReelOv!==null?foncierReelOv:foncierReelCalc;
      return prixReelVal-foncierReel;
    }catch(_){return 0;}
  },[fiches]);


  const inp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:6,color:"#1e293b",padding:"6px 8px",fontSize:13,width:"100%"};

  // Lots VENDU
  const lots=React.useMemo(()=>{
    const fiche=fiches[projId];
    if(!fiche||!fiche.lots) return [];
    return fiche.lots.map((l,i)=>({idx:i,lot:l})).filter(({lot})=>
      ["VENDU","AV10","AV40","AV70","AV95","LIVRE"].includes(lot.statutCommercial||"")
    );
  },[projId,fiches]);

  const fiche=fiches[projId]||{};
  const proj=(projects||[]).find(p=>p.id===projId)||{};
  const lotInfo=lots.find(l=>l.idx===parseInt(lotIdx));
  const lot=lotInfo?.lot||{};
  const programmeNom=[proj.ville,proj.nom].filter(Boolean).join(' ')||fiche.adresse||'';

  // Infos contact client + CGP
  // Email client : lookup CRM par clientId (le lot.clientEmail redondant a été
  // supprimé au Sprint A3 — source de vérité = crm.clients).
  const clientFromCrm=lot.clientId?(crm?.clients||[]).find(c=>c.id===lot.clientId):null;
  const clientEmail=clientFromCrm?.email||'';
  const cgpId=lot.cgpId||'';
  const cgp=(crm?.cgps||[]).find(c=>c.id===cgpId)||null;
  const cgpEmail=cgp?(cgp.emails||'').split(/[,;\s]/)[0].trim():'';

  // ── Sprint C : données pour la modale de replanification ─────────────────
  const lotN=lotIdx!==""?parseInt(lotIdx,10)+1:null;
  const lotRow=React.useMemo(()=>{
    if(!projId||lotN==null) return null;
    return (rows||[]).find(r=>r.projetId===projId&&r.label===`Lot ${lotN}`)||null;
  },[rows,projId,lotN]);
  const lotRowValues=React.useMemo(()=>{
    if(!lotRow) return [];
    return (values||[]).filter(v=>v.rowId===lotRow.id);
  },[values,lotRow]);
  const prixReelLot=Number(lot.prixReel||0);

  // Charger appel EG dès que projId+lotIdx sont connus
  React.useEffect(()=>{
    if(!projId||lotIdx===""){setAppel(null);setAppelErr("");setNoAppelPending(false);return;}
    const lotId=String(parseInt(lotIdx)+1);
    setAppelLoading(true); setAppelErr(""); setAppel(null); setNoAppelPending(false);
    fetch(`/api/appels-eg?programme_id=${encodeURIComponent(projId)}&lot_id=${encodeURIComponent(lotId)}`)
      .then(r=>r.json())
      .then(rows=>{
        const pending=(rows||[]).filter(r=>r.arch_email_sent&&!r.appel_client_sent);
        // Pas d'appel en attente : on n'arrête pas le flux, on propose une
        // création ad-hoc (cas historique d'appels créés hors système).
        if(pending.length===0) setNoAppelPending(true);
        else setAppel(pending[0]);
      })
      .catch(e=>setAppelErr(e.message))
      .finally(()=>setAppelLoading(false));
  },[projId,lotIdx]);

  // Auto-calcul montant_client ad-hoc : pct_delta × travauxReel / 100
  // Ne recalcule que si l'utilisateur n'a pas saisi le montant manuellement.
  React.useEffect(()=>{
    if(adhocMontantManual) return;
    const pct=parseFloat(adhocPct);
    if(isNaN(pct)||pct<=0||!projId||lotIdx==="") return;
    const travauxReel=calcTravauxReelClient(projId,lotIdx);
    if(travauxReel>0) setAdhocMontant((Math.round(travauxReel*pct/100*100)/100).toFixed(2));
  },[adhocPct,projId,lotIdx,adhocMontantManual,calcTravauxReelClient]);

  // Crée un appel ad-hoc via POST /api/appels-eg, marqué directement arch_email_sent=TRUE
  // pour qu'il prenne la place de l'appel en attente.
  const handleCreateAdhocAppel=async()=>{
    if(!projId||lotIdx===""){setAppelErr("Sélectionnez programme + lot d'abord.");return;}
    const pct=parseFloat(adhocPct);
    const montant=parseFloat(adhocMontant);
    const attestPct=adhocAttestPct?parseFloat(adhocAttestPct):pct;
    if(isNaN(pct)||pct<=0){setAppelErr("Pct delta invalide.");return;}
    if(isNaN(montant)||montant<=0){setAppelErr("Montant client invalide.");return;}
    setAdhocCreating(true); setAppelErr("");
    try{
      const lotId=String(parseInt(lotIdx,10)+1);
      const body={
        programme_id:projId,
        lot_id:lotId,
        pct_delta:pct,
        montant_client:montant,
        attestation_pct:attestPct,
        attestation_date:new Date().toISOString().slice(0,10),
        // Sprint F-f : lier l'appel ad-hoc à la notif d'attestation source.
        // On ne pose l'ID que si on a effectivement chargé une attestation
        // depuis cette notif (pattern identique à ligne 2332 / handleReplanConfirm).
        attest_notif_id:attestPdf?notif.id:null,
      };
      const r=await fetch("/api/appels-eg",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
      const newAppel=await r.json();
      if(!r.ok||newAppel.error) throw new Error(newAppel.error||"Erreur création appel");
      // Marquer arch_email_sent=TRUE (l'appel ad-hoc saute l'étape 3 du workflow)
      await fetch(`/api/appels-eg/${newAppel.id}`,{
        method:"PATCH",headers:{"Content-Type":"application/json"},
        body:JSON.stringify({arch_email_sent:true,arch_email_date:new Date().toISOString()}),
      });
      setAppel({...newAppel,arch_email_sent:true});
      setNoAppelPending(false);
    }catch(e){setAppelErr(e.message);}
    setAdhocCreating(false);
  };

  // ── PATCH appel pour réaligner pct_delta / montant_client sur l'attestation
  // Appelé depuis le récap cohérence quand l'utilisateur clique « Mettre à jour ».
  // Recalcule montant_client = travauxReel × newPctDelta / 100.
  // Efface la facture déjà générée pour forcer une regénération sur les bonnes
  // valeurs.
  const handleUpdateAppel=async()=>{
    if(!appel||!projId||lotIdx==="") return;
    const avancAttest=parseFloat(params.avancement_pct!=null?params.avancement_pct:meta.avancement_pct);
    if(isNaN(avancAttest)){setUpdateAppelErr("Avancement attestation manquant — impossible de réaligner.");return;}
    const pctCumuleAppel=Number(appel.pct_cumule||0);
    const newPctDelta=params.pct_appele!=null?Number(params.pct_appele):(avancAttest-pctCumuleAppel);
    if(!(newPctDelta>0)){setUpdateAppelErr("Le nouveau pct_delta serait ≤ 0 — vérifiez l'attestation.");return;}
    const travauxReel=calcTravauxReelClient(projId,lotIdx);
    if(!(travauxReel>0)){setUpdateAppelErr("Impossible de recalculer le montant (prixReel/foncier manquants).");return;}
    const newMontant=Math.round(travauxReel*newPctDelta/100*100)/100;
    setUpdatingAppel(true); setUpdateAppelErr("");
    try{
      const r=await fetch(`/api/appels-eg/${appel.id}`,{
        method:"PATCH",
        headers:{"Content-Type":"application/json"},
        body:JSON.stringify({
          pct_delta:newPctDelta,
          montant_client:newMontant,
          attestation_pct:avancAttest,
        }),
      });
      const d=await r.json();
      if(!r.ok||d.error) throw new Error(d.error||"Erreur PATCH appel");
      // PATCH ne retourne pas pct_cumule (champ calculé) — on le préserve
      setAppel({...d,pct_cumule:appel.pct_cumule});
      setFacturePdfs([]); // force la regénération avec les bons chiffres
    }catch(e){setUpdateAppelErr("Mise à jour échouée : "+e.message);}
    setUpdatingAppel(false);
  };

  // Charger attestation PDF depuis la notification
  const loadAttestation=React.useCallback(async()=>{
    setLoadingAttest(true);
    try{
      const resp=await fetch(`/api/agent/notifications/${notif.id}/attachment`);
      if(!resp.ok){setLoadingAttest(false);return;}
      const blob=await resp.blob();
      const base64=await new Promise(resolve=>{
        const reader=new FileReader();
        reader.onloadend=()=>resolve(reader.result.split(',')[1]);
        reader.readAsDataURL(blob);
      });
      let filename='attestation.pdf';
      try{const ref=JSON.parse(notif.attachment_ref||'{}');filename=ref.filename||filename;}catch(_){}
      setAttestPdf({filename,contentType:'application/pdf',content:base64});
    }catch(e){console.warn('Chargement attestation échoué:',e.message);}
    setLoadingAttest(false);
  },[notif.id,notif.attachment_ref]);

  React.useEffect(()=>{loadAttestation();},[loadAttestation]);

  const handleGeneratePdf=async()=>{
    if(!appel) return;
    setGenerating(true); setSendErr("");
    try{
      const r=await fetch(`/api/appels-eg/${appel.id}/generate-facture-client`,{
        method:"POST",
        headers:{"Content-Type":"application/json"},
        body:JSON.stringify({folderPath:attestFolderPath||null}),
      });
      const d=await r.json();
      if(!r.ok||d.error) throw new Error(d.error||"Erreur génération");
      // Sprint Indivision-4 : la route peut renvoyer {pdf,filename} (legacy seul/couple)
      // ou {pdfs:[{pdf,filename,recipientEmail,recipientName,quotePartPct,...}]} (indivision).
      if(Array.isArray(d.pdfs)&&d.pdfs.length>0){
        setFacturePdfs(d.pdfs);
      }else if(d.pdf&&d.filename){
        setFacturePdfs([{pdf:d.pdf,filename:d.filename}]);
      }else{
        throw new Error("Réponse inattendue du serveur (pdf|pdfs manquant)");
      }
    }catch(e){setSendErr(e.message);}
    setGenerating(false);
  };

  const handlePreviewPdf=React.useCallback((idx)=>{
    const target=facturePdfs[idx??0];
    if(!target) return;
    const chars=atob(target.pdf);
    const bytes=new Uint8Array(chars.length);
    for(let i=0;i<chars.length;i++) bytes[i]=chars.charCodeAt(i);
    const blob=new Blob([bytes],{type:'application/pdf'});
    window.open(URL.createObjectURL(blob),'_blank');
  },[facturePdfs]);

  const emailBody=React.useMemo(()=>{
    if(!appel) return '';
    const isIndivision=facturePdfs.length===2;
    // Nom à utiliser dans la salutation
    let clientNom;
    if(isIndivision){
      clientNom=`${facturePdfs[0].recipientName||'[Acquéreur 1]'} et ${facturePdfs[1].recipientName||'[Acquéreur 2]'}`;
    }else{
      clientNom=[lot.clientNom,lot.conjointNomPrenom||lot.clientNomConjoint].filter(Boolean).join(' & ')||'[Client]';
    }
    const pct=appel.pct_delta||appel.eg_pct||0;
    const montant=appel.montant_client?Number(appel.montant_client).toLocaleString('fr-FR',{minimumFractionDigits:2,maximumFractionDigits:2}):'';
    const iban=fiche.ibanProgramme||'[IBAN À COMPLÉTER]';
    const bic=fiche.bic||'';
    const lotN=parseInt(lotIdx)+1;
    const fmtEur=n=>Number(n||0).toLocaleString('fr-FR',{minimumFractionDigits:2,maximumFractionDigits:2});

    // Bloc spécifique indivision : détail par investisseur (quote-part + montant)
    const indivisionBlock=isIndivision?`

Acquisition en indivision — détail par investisseur :
${facturePdfs.map(p=>`  • ${p.recipientName} — Quote-part ${p.quotePartPct}% — Montant dû ${fmtEur(p.montant)} €`).join('\n')}

Chaque investisseur trouvera sa facture nominative en pièce jointe.`:'';

    const facturesIntro=isIndivision
      ?`- Les factures correspondant à l'appel de fonds de ${pct}% des travaux${montant?`, d'un montant total de ${montant} €`:''}`
      :`- La facture correspondant à l'appel de fonds de ${pct}% des travaux${montant?`, d'un montant de ${montant} €`:''}`;

    return `Madame, Monsieur ${clientNom},

Suite à l'avancement des travaux du programme ${programmeNom}, nous avons le plaisir de vous adresser :
${facturesIntro}
- L'attestation d'avancement délivrée par l'architecte
${indivisionBlock}

INFORMATIONS :
─────────────────────────────────────────
  Programme   : ${programmeNom}
  Lot N°      : ${lotN}${lot.type?` (${lot.type})`:''}
  Avancement  : ${pct}%${montant?`\n  Montant     : ${montant} €`:''}
─────────────────────────────────────────

Conformément à nos conditions générales, nous vous demandons de bien vouloir procéder au règlement à réception par virement :
  IBAN : ${iban}${bic?`\n  BIC  : ${bic}`:''}

Nous restons à votre disposition pour tout renseignement.

Cordialement,`;
  },[appel,lot,lotIdx,programmeNom,fiche,facturePdfs]);

  // ── Étape 5 (Sprint C) ────────────────────────────────────────────────────
  // Le clic sur « Envoyer » ouvre la modale de replanification du plan. Elle
  // appelle ensuite handleReplanConfirm qui exécute /replan (atomique : maj plan
  // + appel_client_sent=TRUE) puis envoie l'email.
  // Sprint Indivision-4 : en indivision, on utilise les recipientEmail renvoyés par
  // le backend ; sinon (seul/couple) on utilise clientEmail du lot.
  const isIndivisionFactures=facturePdfs.length===2;
  const recipientsTo=React.useMemo(()=>{
    if(isIndivisionFactures){
      return facturePdfs.map(p=>p.recipientEmail).filter(Boolean);
    }
    return clientEmail?[clientEmail]:[];
  },[isIndivisionFactures,facturePdfs,clientEmail]);

  const handleSend=()=>{
    if(facturePdfs.length===0){setSendErr("Générez d'abord la facture PDF.");return;}
    if(isIndivisionFactures){
      if(recipientsTo.length<2){
        const missing=facturePdfs.filter(p=>!p.recipientEmail).map(p=>p.recipientName||'?').join(', ');
        setSendErr(`Email manquant pour l'un des investisseurs (indivision) : ${missing}. Renseignez l'email dans la fiche CRM.`);
        return;
      }
    }else{
      if(!clientEmail){setSendErr("Email client introuvable sur ce lot.");return;}
    }
    if(!lotRow){setSendErr(`Ligne « Lot ${lotN} » introuvable dans le plan de trésorerie — impossible de replanifier.`);return;}
    if(!(prixReelLot>0)){setSendErr("prixReel du lot non renseigné — impossible de vérifier l'invariant.");return;}
    setSendErr("");
    setReplanOpen(true);
  };

  // Appelé par AppelReplanModal après validation utilisateur.
  // 1. POST /api/appels-eg/:id/replan (transaction : update plan_values + set appel_client_sent)
  // 2. Si OK : ferme la modale, envoie l'email, marque sent, appelle onDone.
  // Si l'email échoue après /replan : l'appel est marqué sent en DB ; on remonte
  // l'erreur au parent pour permettre une action manuelle (sans rejouer /replan).
  const handleReplanConfirm=async(changes,attestNotifIdArg)=>{
    // 1. /replan (atomique côté serveur)
    const rr=await fetch(`/api/appels-eg/${appel.id}/replan`,{
      method:"POST",headers:{"Content-Type":"application/json"},
      body:JSON.stringify({changes,attestNotifId:attestNotifIdArg||null}),
    });
    const rd=await rr.json();
    if(!rr.ok||rd.error) throw new Error(rd.error||"Erreur lors de la replanification");

    // Sprint F-d : l'appel vient de passer appel_client_sent=TRUE, donc pct_cumule
    // change côté DB. On signale aux autres composants (FichesProgrammes notamment)
    // qu'ils doivent rafraîchir le badge % avancement.
    window.dispatchEvent(new CustomEvent('appels-eg-updated',{detail:{programme_id:projId,lot_id:String(parseInt(lotIdx,10)+1)}}));

    // 2. Email — fermeture de la modale avant l'envoi pour rendre la main visuellement
    setReplanOpen(false);
    setSending(true);
    try{
      // Sprint Indivision-4 : 1 ou 2 factures + attestation. `to` = liste des destinataires
      // séparés par virgule (Graph API et Nodemailer acceptent ce format nativement).
      const attachments=facturePdfs.map(p=>({filename:p.filename,contentType:'application/pdf',content:p.pdf}));
      if(attestPdf) attachments.push({filename:attestPdf.filename,contentType:attestPdf.contentType,content:attestPdf.content});
      const pct=appel.pct_delta||appel.eg_pct||0;
      const toField=recipientsTo.join(', ');
      const payload={
        to:toField,
        subject:`Appel de fonds ${pct}% travaux — ${programmeNom} — Lot ${lotN}`,
        body:emailBody,
        attachments,
      };
      if(cgpEmail) payload.cc=cgpEmail;
      const r=await fetch("/api/email/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(payload)});
      const d=await r.json();
      if(!r.ok||d.error) throw new Error(d.error||"Erreur envoi");
      setSent(true);
      await onDone();
    }catch(e){
      // L'appel est déjà marqué envoyé côté DB (par /replan). L'email lui n'est
      // pas parti. On le signale clairement à l'utilisateur.
      setSendErr(`Plan mis à jour avec succès, mais l'envoi email a échoué : ${e.message}. L'appel est marqué comme envoyé en base. Renvoyez le mail manuellement si nécessaire.`);
      setSending(false);
    }
  };

  const projs=(projects||[]).filter(p=>!p.isGlobal&&(!p.statut||p.statut==='En cours'));

  return(<>
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:3000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}
      onClick={e=>{if(e.target===e.currentTarget)onClose();}}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:560,maxWidth:"96vw",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}}
        onClick={e=>e.stopPropagation()}>

        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>💶 Générer appel de fonds client</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:14}}>Attestation reçue : <b>{notif.subject}</b></div>

        {/* Sélection programme + lot */}
        <div style={{display:"flex",flexDirection:"column",gap:10,marginBottom:14}}>
          <div>
            <div style={{fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Programme *</div>
            <select value={projId} onChange={e=>{setProjId(e.target.value);setLotIdx("");setAppel(null);setAppelErr("");setFacturePdfs([]);}} style={inp}>
              <option value="">— Sélectionner —</option>
              {projs.map(p=><option key={p.id} value={p.id}>{p.ville?p.ville+" – ":""}{p.nom}</option>)}
            </select>
          </div>
          <div>
            <div style={{fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Lot *</div>
            <select value={lotIdx} onChange={e=>{setLotIdx(e.target.value);setAppel(null);setAppelErr("");setFacturePdfs([]);}} style={inp} disabled={!projId}>
              <option value="">— Sélectionner —</option>
              {lots.map(({idx,lot})=>(
                <option key={idx} value={String(idx)}>
                  Lot {idx+1}{lot.type?" ("+lot.type+")":""}{lot.clientNom?" — "+lot.clientNom:""} [{lot.statutCommercial}]
                </option>
              ))}
            </select>
          </div>
        </div>

        {appelLoading&&<div style={{fontSize:12,color:"#64748b",marginBottom:10}}>⏳ Recherche de l'appel de fonds…</div>}
        {appelErr&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12,color:"#dc2626"}}>❌ {appelErr}</div>}

        {/* ── Cas historique : pas d'appel en attente → création ad-hoc ── */}
        {noAppelPending&&!appel&&(
          <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:"10px 12px",marginBottom:12,fontSize:12}}>
            <div style={{fontWeight:700,color:"#92400e",marginBottom:6}}>⚠️ Aucun appel de fonds en attente pour ce lot.</div>
            <div style={{color:"#78350f",marginBottom:10,lineHeight:1.5}}>
              Si cet email correspond à un appel créé manuellement (cas historique, antérieur à l'automatisation), saisissez les informations ci-dessous pour le rattacher au système :
            </div>
            <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:8,marginBottom:8}}>
              <div>
                <div style={{fontSize:10,fontWeight:700,color:"#78350f",marginBottom:2}}>Pct delta (%) *</div>
                <input type="number" step="0.01" min="0" value={adhocPct} onChange={e=>setAdhocPct(e.target.value)}
                  placeholder="25" style={{...inp,padding:"5px 8px",fontSize:12}}/>
              </div>
              <div>
                <div style={{fontSize:10,fontWeight:700,color:"#78350f",marginBottom:2}}>Montant client (€) *{!adhocMontantManual&&adhocMontant?<span style={{fontWeight:400,color:"#92400e",marginLeft:4}}>(calculé)</span>:null}</div>
                <input type="number" step="0.01" min="0" value={adhocMontant} onChange={e=>{setAdhocMontantManual(true);setAdhocMontant(e.target.value);}}
                  placeholder="25000" style={{...inp,padding:"5px 8px",fontSize:12}}/>
              </div>
              <div>
                <div style={{fontSize:10,fontWeight:700,color:"#78350f",marginBottom:2}}>Attestation pct (%)</div>
                <input type="number" step="0.01" min="0" value={adhocAttestPct} onChange={e=>setAdhocAttestPct(e.target.value)}
                  placeholder="(par défaut = pct delta)" style={{...inp,padding:"5px 8px",fontSize:12}}/>
              </div>
            </div>
            <button onClick={handleCreateAdhocAppel} disabled={adhocCreating||!adhocPct||!adhocMontant}
              style={{background:adhocCreating||!adhocPct||!adhocMontant?"#94a3b8":"#0369a1",color:"#fff",border:"none",borderRadius:6,padding:"6px 14px",cursor:adhocCreating||!adhocPct||!adhocMontant?"default":"pointer",fontSize:12,fontWeight:700}}>
              {adhocCreating?"⏳ Création…":"+ Créer un appel ad-hoc"}
            </button>
          </div>
        )}

        {appel&&!appelErr&&<>
          {/* Récapitulatif appel */}
          <div style={{background:"#fef3c7",border:"1px solid #fde68a",borderRadius:8,padding:"10px 12px",marginBottom:12,fontSize:12}}>
            <b>Appel :</b> Lot {parseInt(lotIdx)+1} · <b>{appel.pct_delta||appel.eg_pct}%</b>
            {appel.montant_client&&<span style={{marginLeft:8}}>· {Number(appel.montant_client).toLocaleString("fr-FR",{minimumFractionDigits:2,maximumFractionDigits:2})} €</span>}
          </div>

          {/* ── Cohérence attestation ↔ appel ── */}
          {(()=>{
            const avancAttestRaw=params.avancement_pct!=null?params.avancement_pct:meta.avancement_pct;
            const avancAttest=parseFloat(avancAttestRaw);
            if(isNaN(avancAttest)) return null;
            const pctCumuleAppel=Number(appel.pct_cumule||0);
            const pctDeltaAppel=Number(appel.pct_delta||appel.eg_pct||0);
            const deltaTheorique=Math.round((avancAttest-pctCumuleAppel)*100)/100;
            const pctAppeleSaisi=params.pct_appele!=null?Number(params.pct_appele):deltaTheorique;
            const ecart=Math.round((pctAppeleSaisi-pctDeltaAppel)*100)/100;
            const isCoherent=Math.abs(ecart)<0.01;
            const travauxReel=calcTravauxReelClient(projId,lotIdx);
            const newMontant=travauxReel>0?Math.round(travauxReel*pctAppeleSaisi/100*100)/100:null;
            const bg=isCoherent?"#f0fdf4":"#fff7ed";
            const bd=isCoherent?"#86efac":"#fed7aa";
            const titleC=isCoherent?"#15803d":"#92400e";
            return(
              <div style={{background:bg,border:`1px solid ${bd}`,borderRadius:8,padding:"10px 12px",marginBottom:12,fontSize:12}}>
                <div style={{fontWeight:700,marginBottom:6,color:titleC}}>
                  {isCoherent?"✅ Cohérence attestation ↔ appel":"⚠️ Écart entre l'attestation et l'appel"}
                </div>
                <div style={{display:"grid",gridTemplateColumns:"auto auto",columnGap:14,rowGap:3,fontSize:11,lineHeight:1.5}}>
                  <span style={{color:"#475569"}}>Avancement attestation :</span><b>{avancAttest}%</b>
                  <span style={{color:"#475569"}}>Cumul déjà appelé :</span><b>{pctCumuleAppel}%</b>
                  <span style={{color:"#475569"}}>Delta théorique :</span><b>{deltaTheorique}%</b>
                  <span style={{color:"#475569"}}>Delta de cet appel (DB) :</span>
                  <b style={{color:isCoherent?"inherit":"#dc2626"}}>
                    {pctDeltaAppel}%
                    {!isCoherent&&pctAppeleSaisi!==pctDeltaAppel&&
                      <span style={{fontWeight:400,marginLeft:6,color:"#7c4700"}}>(voulu : {pctAppeleSaisi}%)</span>}
                  </b>
                  {!isCoherent&&<>
                    <span style={{color:"#475569"}}>Écart :</span>
                    <b style={{color:"#dc2626"}}>{ecart>0?"+":""}{ecart}%</b>
                  </>}
                </div>
                {!isCoherent&&newMontant!==null&&pctAppeleSaisi>0&&(
                  <div style={{marginTop:10}}>
                    <button onClick={handleUpdateAppel} disabled={updatingAppel}
                      style={{background:updatingAppel?"#94a3b8":"#0369a1",color:"#fff",border:"none",borderRadius:6,padding:"6px 14px",fontSize:12,fontWeight:700,cursor:updatingAppel?"default":"pointer"}}>
                      {updatingAppel?"⏳ Mise à jour…":`↻ Mettre à jour l'appel à ${pctAppeleSaisi}% (${fmtEur(newMontant)} €)`}
                    </button>
                    <div style={{fontSize:10,color:"#7c4700",marginTop:4,lineHeight:1.4}}>
                      pct_delta et montant_client seront recalculés ; la facture devra être regénérée.
                    </div>
                    {updateAppelErr&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:6,padding:"6px 10px",fontSize:11,color:"#dc2626",marginTop:6}}>❌ {updateAppelErr}</div>}
                  </div>
                )}
              </div>
            );
          })()}

          {/* PJ attestation */}
          <div style={{marginBottom:10}}>
            {loadingAttest&&<div style={{fontSize:11,color:"#64748b"}}>⏳ Chargement attestation…</div>}
            {attestPdf&&!loadingAttest&&(
              <div style={{background:"#ecfdf5",border:"1px solid #6ee7b7",borderRadius:6,padding:"6px 10px",fontSize:11}}>
                📎 Attestation : <b>{attestPdf.filename}</b>
              </div>
            )}
            {!attestPdf&&!loadingAttest&&(
              <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:6,padding:"6px 10px",fontSize:11,color:"#92400e"}}>
                ⚠️ Aucune PJ attestation sur cette notification.
              </div>
            )}
          </div>

          {/* Infos client — en indivision, on affiche les 2 destinataires */}
          <div style={{background:"#f0f9ff",border:"1px solid #bae6fd",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12}}>
            {isIndivisionFactures?(<>
              <b>Indivision — destinataires :</b>
              {facturePdfs.map((p,i)=>(
                <div key={i} style={{fontFamily:"monospace",marginTop:2}}>
                  • {p.recipientName} — {p.recipientEmail||"⚠️ Email manquant"} ({p.quotePartPct}%)
                </div>
              ))}
            </>):(<>
              <b>Client :</b> <span style={{fontFamily:"monospace"}}>{clientEmail||"⚠️ Email manquant"}</span>
            </>)}
            {cgpEmail&&<><br/><b>CGP (CC) :</b> <span style={{fontFamily:"monospace"}}>{cgpEmail}</span></>}
          </div>

          {/* Générer PDF(s) */}
          {facturePdfs.length===0?(
            <div style={{marginBottom:14}}>
              <button onClick={handleGeneratePdf} disabled={generating}
                style={{background:generating?"#94a3b8":"#7c3aed",color:"#fff",border:"none",borderRadius:8,padding:"8px 18px",fontSize:13,fontWeight:700,cursor:generating?"default":"pointer"}}>
                {generating?"⏳ Génération…":"📄 Générer la facture client PDF"}
              </button>
            </div>
          ):(
            <div style={{background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,padding:"6px 12px",marginBottom:12,fontSize:12,display:"flex",flexDirection:"column",gap:6}}>
              {facturePdfs.map((p,i)=>(
                <div key={i} style={{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8}}>
                  <span>✅ Facture {facturePdfs.length===2?`(${p.quotePartPct}%) `:''}générée : <b>{p.filename}</b></span>
                  <button onClick={()=>handlePreviewPdf(i)}
                    style={{background:"#2563eb",color:"#fff",border:"none",borderRadius:6,padding:"4px 12px",fontSize:12,fontWeight:600,cursor:"pointer",whiteSpace:"nowrap"}}>
                    👁 Prévisualiser
                  </button>
                </div>
              ))}
            </div>
          )}

          {/* Corps email */}
          <div style={{marginBottom:14}}>
            <div style={{fontSize:11,color:"#64748b",marginBottom:4}}>Corps de l'email client</div>
            <textarea value={emailBody} readOnly
              style={{background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:8,width:"100%",height:200,
                fontFamily:"'Courier New',monospace",fontSize:11,padding:"8px",resize:"vertical",lineHeight:1.5}}/>
          </div>

          {sendErr&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#dc2626"}}>❌ {sendErr}</div>}
          {sent&&<div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"10px 12px",marginBottom:10,fontSize:12,color:"#15803d"}}>✅ Email envoyé au client.</div>}

          <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
            <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>
              {sent?"Fermer":"Annuler"}
            </button>
            {!sent&&(()=>{
              // Sprint Indivision-4 : conditions de désactivation
              // - seul/couple : !clientEmail (1 destinataire requis)
              // - indivision  : recipientsTo.length < 2 (les 2 emails requis)
              const missingEmail=isIndivisionFactures?(recipientsTo.length<2):(!clientEmail);
              const disabled=sending||facturePdfs.length===0||missingEmail||!lotRow||!(prixReelLot>0);
              return(
                <button onClick={handleSend} disabled={disabled}
                  style={{background:disabled?"#94a3b8":"#0369a1",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:disabled?"default":"pointer"}}>
                  {sending?"⏳ Envoi email…":"📅 Replanifier & envoyer"}
                </button>
              );
            })()}
          </div>
        </>}

        {!appel&&!appelLoading&&!appelErr&&(projId===""||lotIdx==="")&&(
          <div style={{textAlign:"right",marginTop:16}}>
            <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>Fermer</button>
          </div>
        )}

      </div>
    </div>

    {/* Sprint C — modale de replanification (étape 5) */}
    {replanOpen&&appel&&lotRow&&prixReelLot>0&&(
      <AppelReplanModal
        appel={appel}
        prixReel={prixReelLot}
        currentRowValues={lotRowValues}
        lotN={lotN}
        programmeNom={programmeNom}
        attestNotifId={attestPdf?notif.id:null}
        onConfirm={handleReplanConfirm}
        onCancel={()=>setReplanOpen(false)}
      />
    )}
  </>);
}

/* ── Modale : email LIA incomplète ──────────────────────────────────────────── */
function IncompleteLiaModal({state,onSent,onClose}){
  const {loading,emailData}=state;
  const [to,   setTo]   =React.useState("");
  const [cc,   setCc]   =React.useState("");
  const [subj, setSubj] =React.useState("");
  const [body, setBody] =React.useState("");
  const [sending,setSending]=React.useState(false);

  React.useEffect(()=>{
    if(!emailData) return;
    setTo(emailData.to||"");
    setCc(emailData.cc||"");
    setSubj(emailData.subject||"");
    setBody(emailData.body||"");
  },[emailData]);

  const inp={width:"100%",padding:"7px 10px",border:"1px solid #e2e8f0",borderRadius:6,fontSize:13,
    background:"#f8fafc",color:"#1e293b",boxSizing:"border-box"};
  const btnS=(bg)=>({background:bg,color:"#fff",border:"none",borderRadius:7,
    padding:"7px 18px",cursor:"pointer",fontSize:13,fontWeight:600});

  const handleSend=async()=>{
    if(!to.trim()){alert("Le destinataire (À) est requis.");return;}
    setSending(true);
    try{
      const payload={to:to.trim(),subject:subj.trim(),body:body.trim(),bodyType:"Text"};
      if(cc.trim()) payload.cc=cc.trim();
      if(emailData && emailData.attachment){
        payload.attachment={
          filename:    emailData.attachment.filename,
          contentType: emailData.attachment.contentType,
          content:     emailData.attachment.content,
        };
      }
      const r=await fetch("/api/email/send",{
        method:"POST",headers:{"Content-Type":"application/json"},
        body:JSON.stringify(payload)
      });
      const d=await r.json();
      if(!r.ok||d.error){alert("Erreur envoi : "+(d.error||r.statusText));setSending(false);return;}
      await onSent();
    }catch(e){alert("Erreur réseau : "+e.message);setSending(false);}
  };

  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.6)",zIndex:3100,display:"flex",
      alignItems:"center",justifyContent:"center"}}
      onClick={e=>{if(e.target===e.currentTarget&&!sending)onClose();}}>
      <div style={{background:"#fff",borderRadius:14,padding:24,width:"min(620px,96vw)",
        maxHeight:"92vh",overflowY:"auto",boxShadow:"0 8px 40px #0004"}}>

        <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
          <div style={{fontWeight:800,fontSize:16,color:"#1e293b"}}>⚠️ LIA incomplète — informer l’expéditeur</div>
          <button onClick={onClose} disabled={sending}
            style={{background:"none",border:"none",fontSize:20,cursor:"pointer",color:"#64748b"}}>✕</button>
        </div>

        {loading&&(
          <div style={{textAlign:"center",padding:"32px 0",color:"#64748b",fontSize:13}}>
            ⏳ Préparation de l’email…
          </div>
        )}

        {!loading&&emailData&&(
          <>
            <div style={{marginBottom:10}}>
              <label style={{display:"block",fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>À *</label>
              <input value={to} onChange={e=>setTo(e.target.value)} style={inp} placeholder="adresse@exemple.fr"/>
            </div>
            <div style={{marginBottom:10}}>
              <label style={{display:"block",fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>CC</label>
              <input value={cc} onChange={e=>setCc(e.target.value)} style={inp} placeholder="copie1@ex.fr, copie2@ex.fr"/>
            </div>
            <div style={{marginBottom:10}}>
              <label style={{display:"block",fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Sujet</label>
              <input value={subj} onChange={e=>setSubj(e.target.value)} style={inp}/>
            </div>
            <div style={{marginBottom:12}}>
              <label style={{display:"block",fontSize:11,fontWeight:700,color:"#64748b",marginBottom:3}}>Corps du message</label>
              <textarea value={body} onChange={e=>setBody(e.target.value)} rows={10}
                style={{...inp,resize:"vertical",fontFamily:"inherit",lineHeight:1.55}}/>
            </div>
            <div style={{background:"#f0f9ff",border:"1px solid #bae6fd",borderRadius:8,
              padding:"8px 12px",marginBottom:16,fontSize:12,color:"#0369a1"}}>
              {emailData.hasAttachment
                ? <>📎 La LIA reçue sera jointe automatiquement ({emailData.attachment&&emailData.attachment.filename||"document"})</>
                : <>⚠ La pièce jointe originale est inaccessible — l’email sera envoyé sans PJ.</>
              }
            </div>
            <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
              <button onClick={onClose} disabled={sending}
                style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:7,
                  padding:"6px 16px",fontSize:13,cursor:"pointer",color:"#475569",fontWeight:600}}>
                Annuler
              </button>
              <button onClick={handleSend} disabled={sending||!to.trim()} style={btnS(sending?"#94a3b8":"#f59e0b")}>
                {sending?"⏳ Envoi…":"📧 Envoyer"}
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}
