const { useState, useMemo, useRef, useCallback, useEffect } = React;

const C = {
  bg:"#f8fafc", card:"#ffffff", border:"#e2e8f0", text:"#1e293b",
  muted:"#64748b", accent:"#2563eb", green:"#16a34a", red:"#dc2626", blue:"#2563eb",
};
const STATUT_STYLE = { R:{bg:"#dcfce7",color:"#15803d"}, F:{bg:"#dbeafe",color:"#1d4ed8"} };
const USERS = ["Direction","Associé","Comptable"];
const PROJ_COLORS = ["#3b82f6","#a855f7","#22c55e","#f59e0b","#ef4444","#14b8a6","#f97316","#ec4899","#06b6d4","#84cc16","#fb7185","#34d399","#e879f9","#8b5cf6","#64748b"];
const uid = () => Math.random().toString(36).slice(2,9);
const STATUS_ORDER = ["En cours","Livré","Abandonné"];

const COUT_ROWS_LABELS = [
  "GFA","DO / TRC","Dossier banque","Crédit mensuel","MOE","Travaux",
  "DP","PC","Plaquette","Déplacements","Avocat",
  "Homme de l'art","Géomètre","Diagnostics","Loyer meublé",
  "Marge CGP","Marge Blue","Taxe foncière","Eau et électricité","Autres"
];
const ROLES_INTERVENANTS = [
  "MOE",
  "Entrepreneur général",
  "Architecte",
  "Diagnostiqueur",
  "Géomètre",
  "Avocat",
  "Notaire",
  "Bureau de contrôle",
  "Assurance DO",
  "Assurance GFA",
  "Autre",
];
const RELATION_TYPES = ["Célibataire", "Époux(se)", "Concubin(ne)", "PACS"];
const TRAVAUX_SCHEDULE = [
  {offsetWeeks:3,pct:0.10},{offsetWeeks:9,pct:0.30},{offsetWeeks:15,pct:0.30},
  {offsetWeeks:21,pct:0.25},{offsetWeeks:27,pct:0.05},
];
const STATUTS_COMMERCIAL = [
  {key:"LIBRE",    label:"Libre",              bg:"#f1f5f9",color:"#64748b"},
  {key:"OPTION",   label:"Optionné",           bg:"#fef3c7",color:"#b45309"},
  {key:"LIA",      label:"Sous LIA",           bg:"#dbeafe",color:"#1d4ed8"},
  {key:"COMPROMIS",label:"Sous compromis",     bg:"#f3e8ff",color:"#7c3aed"},
  {key:"VENDU",    label:"Vendu",              bg:"#dcfce7",color:"#15803d"},
  {key:"AV10",     label:"10% d'avancement",   bg:"#fef9c3",color:"#713f12"},
  {key:"AV40",     label:"40% d'avancement",   bg:"#ffedd5",color:"#9a3412"},
  {key:"AV70",     label:"70% d'avancement",   bg:"#fed7aa",color:"#c2410c"},
  {key:"AV95",     label:"95% d'avancement",   bg:"#fde68a",color:"#92400e"},
  {key:"LIVRE",    label:"Livré",              bg:"#bfdbfe",color:"#1d4ed8"},
];
/* Mapping statut → index dans APPEL_STAGE_LABELS / lotVals pour l'appel de fonds */
const STATUS_TO_CALL_IDX = {VENDU:0,AV10:1,AV40:2,AV70:3,AV95:4,LIVRE:5};

const EMPTY_LOT = () => ({ surface:"", ext:"", type:"", loyerPinel:"", prixBrutO:"", ponderationPct:"", prixFinalO:"", prixReel:"", foncierReelO:"", clientId:"", clientNom:"", cgpId:"", cgpNom:"", retroPctR:"", retroMontantR:"", statutCommercial:"LIBRE", historiqueStatut:[], typeAcquisition:"seul", coAcheteurClientId:"", quotePartPrincipalPct:50 });
const EMPTY_COMPTE_BANCAIRE=()=>({id:uid(),banque:"",libelle:"",iban:""});
const EMPTY_ENTREPRISE=()=>({id:uid(),nom:"",activite:"",adresseRue:"",codePostal:"",ville:"",pays:"France",emails:"",siren:"",iban:"",notes:"",contacts:[],comptesBancaires:[]});
const EMPTY_CLIENT=()=>({id:uid(),nom:"",prenom:"",adresseRue:"",codePostal:"",ville:"",pays:"France",email:"",telephone:"",dateNaissance:"",lieuNaissance:"",profession:"",cgpId:"",notes:"",comptesBancaires:[],typeRelation:"Célibataire",regimeMatrimonial:"",conjointNom:"",conjointPrenom:"",conjointTelephone:"",conjointEmail:"",conjointDateNaissance:"",conjointLieuNaissance:"",conjointProfession:""});
const EMPTY_CGP=()=>({id:uid(),societe:"",nom:"",prenom:"",adresseRue:"",codePostal:"",ville:"",pays:"France",emails:"",telephone:"",notes:"",comptesBancaires:[]});
const EMPTY_BANQUE=()=>({id:uid(),nom:"",adresseRue:"",codePostal:"",ville:"",pays:"France",iban:"",notes:"",contacts:[]});
const EMPTY_CONTACT=()=>({id:uid(),nom:"",prenom:"",fonction:"",email:"",telephone:""});
const EMPTY_COMPTE=()=>({id:uid(),numero:"",libelle:"",type:"Charge"});
const EMPTY_FLUX=()=>({id:uid(),programmeId:"",rowId:"",emetteurId:"",emetteurNom:"",recepteurId:"",recepteurNom:"",montant:"",dateReception:"",datePaiement:"",compteDebit:"",compteCredit:"",pdfName:"",notes:"",statutFlux:"New"});
const FLUX_STATUTS=[
  {key:"New",     label:"Nouveau",  bg:"#dbeafe",color:"#1d4ed8"},
  {key:"Validée", label:"Validée",  bg:"#dcfce7",color:"#15803d"},
  {key:"Annulée", label:"Annulée",  bg:"#fee2e2",color:"#dc2626"},
];
/* Les identifiants sont gérés côté serveur (table PostgreSQL `users`).
   Aucun mot de passe ni hash dans le code client. */
const COMPTE_TYPES=["Actif","Passif","Charge","Produit","Trésorerie"];

const EMPTY_FICHE = () => ({
  adresse:"", typeOp:"Rénovation", dpOuPc:"DP",
  prixAchat:"", montantCommerceRdc:"", prixDenormandie:"",
  nbLots:"", devisTravaux:"", surfaceTotale:"", partiesCommunes:"", exterieurs:"",
  euribor:"", nbLotsOccupes:"", plafondPinel:"", montantCredit:"",
  gfa:"", doTrc:"", dossierBanque:"", creditMensuel:"",
  dp:"", pc:"", plaquette:"", deplacements:"",
  avocat:"", hommeArt:"", geometre:"", diagnostics:"",
  loyerMeuble:"", margeCGPpct:"", margeBluePct:"",
  gfaR:"", doTrcR:"", dossierBanqueR:"", creditMensuelR:"", moeR:"", travauxR:"",
  dpR:"", pcR:"", plaqueтteR:"", deplacementsR:"",
  avocatR:"", hommeArtR:"", geometreR:"", diagnosticsR:"",
  loyerMeubleR:"", margeCGPpctR:"", margeBluePctR:"",
  taxeFonciere:"", taxeFonciereR:"", eauElectricite:"", eauElectriciteR:"", autres:"", autresR:"",
  plusvalueTravaux:"", plusvalueTravauxR:"",
  dateAcquisition:"", dateDebutTravaux:"", dateLivraison:"", banque:"", ibanProgramme:"", statut:"En cours", notes:"",
  prevalidationGFA: false,
  gfaEmailContact: "",
  lots:[],
});

const fmtEur = (n, compact) => {
  if (n==null||isNaN(n)||n===0) return compact?"":"0 €";
  if (compact && Math.abs(n)>=1_000_000) {
    // ≥1 M : on garde la résolution k€ (1 235 k € au lieu de 1,2 M €)
    return new Intl.NumberFormat("fr-FR").format(Math.round(n/1000))+" k €";
  }
  if (compact && Math.abs(n)>=1000)
    return new Intl.NumberFormat("fr-FR",{notation:"compact",maximumFractionDigits:1}).format(n)+" €";
  return new Intl.NumberFormat("fr-FR",{style:"currency",currency:"EUR",maximumFractionDigits:0}).format(n);
};
const fmtW = (d) => { const p=d.split("-"); return p[2]+"/"+p[1]; };
const toN = (v) => parseFloat(String(v||"").replace(/\s/g,"").replace(",",".")) || 0;

const BASE_MS = Date.UTC(2024,3,22);
const WEEK_MS = 7*24*60*60*1000;
const weekDates = [];
for (let i=0;i<400;i++) { // 400 semaines ≈ jusqu'à oct 2031 (était 200 = jan 2028, trop court pour les programmes longs)
  const d = new Date(BASE_MS+i*WEEK_MS);
  weekDates.push(d.getUTCFullYear()+"-"+String(d.getUTCMonth()+1).padStart(2,"0")+"-"+String(d.getUTCDate()).padStart(2,"0"));
}
const nowMs = Date.UTC(new Date().getUTCFullYear(),new Date().getUTCMonth(),new Date().getUTCDate());
const diffW = Math.floor((nowMs-BASE_MS)/WEEK_MS);
const todayIso = weekDates[Math.max(0,Math.min(diffW,weekDates.length-1))];

const shiftWeek = (isoDate, offset) => {
  if(!isoDate) return null;
  const ms = Date.UTC(parseInt(isoDate.slice(0,4)),parseInt(isoDate.slice(5,7))-1,parseInt(isoDate.slice(8,10)));
  const idx = Math.round((ms-BASE_MS)/WEEK_MS) + offset;
  return weekDates[Math.max(0,Math.min(weekDates.length-1,idx))] || null;
};

/* ── Sprint H : formatage d'adresse CRM ──────────────────────────────────────
   Item peut être un client / entreprise / banque / cgp. Format aplati :
   {adresseRue, codePostal, ville, pays}. Fallback sur item.adresse (legacy
   string multi-ligne) si les champs structurés sont vides. Le pays est omis
   au rendu s'il vaut "France" (cas le plus fréquent). */
function formatAdresse(item) {
  if (!item) return '';
  const rue   = (item.adresseRue || '').trim();
  const cp    = (item.codePostal || '').trim();
  const ville = (item.ville      || '').trim();
  const pays  = (item.pays       || '').trim();
  if (!rue && !cp && !ville) return (item.adresse || '').trim();
  const lines = [];
  if (rue) lines.push(rue);
  const cpVille = [cp, ville].filter(Boolean).join(' ').trim();
  if (cpVille) lines.push(cpVille);
  if (pays && pays.toLowerCase() !== 'france') lines.push(pays);
  return lines.join('\n');
}

/* ── Recherche un contact dans le CRM (nom partiel, insensible à la casse) ── */
function findCrmContact(crm, searchTerms) {
  const terms=(Array.isArray(searchTerms)?searchTerms:[searchTerms]).map(t=>(t||'').toLowerCase().trim()).filter(Boolean);
  const matchStr=s=>terms.some(t=>(s||'').toLowerCase().includes(t));
  for(const c of (crm?.clients||[])){
    if(matchStr(c.nom)||matchStr(c.prenom)||matchStr(`${c.nom} ${c.prenom}`)||matchStr(`${c.prenom} ${c.nom}`)){
      return {nom:`${c.prenom||''} ${c.nom||''}`.trim(),email:c.email||'',telephone:c.telephone||''};
    }
  }
  for(const e of (crm?.entreprises||[])){
    if(matchStr(e.nom)) return {nom:e.nom,email:(e.emails||'').split(/[,;\s]/)[0].trim(),telephone:''};
    for(const c of (e.contacts||[])){
      if(matchStr(c.nom)||matchStr(c.prenom)){
        return {nom:`${c.prenom||''} ${c.nom||''}`.trim(),email:c.email||'',telephone:c.telephone||''};
      }
    }
  }
  for(const c of (crm?.cgps||[])){
    if(matchStr(c.nom)||matchStr(c.prenom)||matchStr(c.societe)){
      return {nom:`${c.prenom||''} ${c.nom||''}`.trim(),email:(c.emails||'').split(/[,;\s]/)[0].trim(),telephone:c.telephone||''};
    }
  }
  for(const b of (crm?.banques||[])){
    for(const c of (b.contacts||[])){
      if(matchStr(c.nom)||matchStr(c.prenom)){
        return {nom:`${c.prenom||''} ${c.nom||''}`.trim(),email:c.email||'',telephone:c.telephone||''};
      }
    }
  }
  return null;
}

/* ── Envoi d'un email via l'API backend ── */
async function sendEmailApi({to,cc,subject,body,attachment}) {
  const resp=await fetch('/api/email/send',{
    method:'POST',
    headers:{'Content-Type':'application/json'},
    body:JSON.stringify({to,cc,subject,body,attachment})
  });
  const data=await resp.json();
  if(!resp.ok) throw new Error(data.error||'Erreur envoi');
  return true;
}

const parseCSV = (text) => {
  const lines = text.split(/\r?\n/);
  if (lines.length < 2) return null;
  const headers = lines[0].split(",");
  const w2026 = (n) => weekDates[89+(n-1)] || "";
  const colDefs = [];
  for (let i=3; i<headers.length; i++) {
    const h = headers[i].trim();
    if (/^\d{4}-\d{2}-\d{2}$/.test(h)) { colDefs.push({idx:i,weekIso:h}); continue; }
    const fr = h.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
    if (fr) { colDefs.push({idx:i,weekIso:fr[3]+"-"+fr[2]+"-"+fr[1]}); continue; }
    const wm = h.match(/^W(\d+)$/);
    if (wm) { const w=w2026(parseInt(wm[1])); if(w) colDefs.push({idx:i,weekIso:w}); }
  }
  if (colDefs.length===0) return null;
  const projects=[], rows=[], values=[];
  const seenProj={};
  let colorIdx=0;
  for (let li=1; li<lines.length; li++) {
    const line=lines[li].trim();
    if (!line) continue;
    const cells=[];
    let cur="", inQ=false;
    for (let ci=0;ci<line.length;ci++) {
      const ch=line[ci];
      if (ch==='"') { inQ=!inQ; }
      else if (ch==="," && !inQ) { cells.push(cur); cur=""; }
      else { cur+=ch; }
    }
    cells.push(cur);
    const ville=(cells[0]||"").trim();
    const nom=(cells[1]||"").trim();
    const statut=(cells[2]||"").trim().toUpperCase();
    if (!nom||(statut!=="S"&&statut!=="L")) continue;
    const isGlobal=ville.toLowerCase()==="global"||ville==="";
    const projKey=isGlobal?"__global":ville;
    if (!seenProj[projKey]) {
      seenProj[projKey]=true;
      projects.push({id:projKey,ville:isGlobal?"":ville,nom:isGlobal?"Global":ville,isGlobal,color:PROJ_COLORS[colorIdx++%PROJ_COLORS.length],statut:"En cours"});
    }
    const rowId=uid();
    rows.push({id:rowId,projetId:projKey,label:nom,statut:statut==="S"?"R":"F"});
    colDefs.forEach(({idx,weekIso})=>{
      const raw=(cells[idx]||"").trim().replace(/\s/g,"");
      if (!raw) return;
      const neg=raw.startsWith("(");
      const clean=raw.replace(/[()]/g,"").replace(/,(\d{3})/g,"$1");
      const n=parseFloat(clean);
      if (!isNaN(n)&&n!==0) values.push({rowId,weekIso,montant:neg?-Math.abs(n):n});
    });
  }
  if (projects.length===0) return null;
  return {projects,rows,values};
};

function StatutBadge({statut,onChange}) {
  const s=STATUT_STYLE[statut];
  return (
    <span onClick={()=>onChange(statut==="R"?"F":"R")}
      style={{display:"inline-block",minWidth:18,textAlign:"center",borderRadius:4,padding:"1px 5px",fontSize:10,fontWeight:700,cursor:"pointer",background:s.bg,color:s.color,userSelect:"none",flexShrink:0}}>
      {statut}
    </span>
  );
}

function StatutLotBadge({statut,onClick}) {
  const s=STATUTS_COMMERCIAL.find(x=>x.key===statut)||STATUTS_COMMERCIAL[0];
  return <span onClick={onClick} style={{display:"inline-block",borderRadius:4,padding:"2px 6px",fontSize:10,fontWeight:700,cursor:"pointer",userSelect:"none",background:s.bg,color:s.color,whiteSpace:"nowrap"}} title="Cliquer pour modifier">{s.label}</span>;
}

function HistoriqueStatutModal({lot,lotIdx,crm,onSave,onClose}) {
  const [newStatut,setNewStatut]=useState("");
  const [newDate,setNewDate]=useState(new Date().toISOString().slice(0,10));
  const [newClient,setNewClient]=useState(lot.clientNom||"");
  const [newNote,setNewNote]=useState("");
  // Sprint Indivision (étape 2) : type d'acquisition + co-acheteur + quote-part
  const [typeAcquisition,setTypeAcquisition]=useState(lot.typeAcquisition||"seul");
  const [coAcheteurClientId,setCoAcheteurClientId]=useState(lot.coAcheteurClientId||"");
  // Quote-parts indépendantes (somme contrôlée à 100%). On initialise à 50/50 par défaut,
  // ou (qpp / 100-qpp) si lot.quotePartPrincipalPct est déjà renseigné.
  const initialQpp = lot.quotePartPrincipalPct!=null && lot.quotePartPrincipalPct!=="" && !isNaN(Number(lot.quotePartPrincipalPct))
    ? Number(lot.quotePartPrincipalPct) : 50;
  const [quotePartPrincipalPct,setQuotePartPrincipalPct]=useState(initialQpp);
  const [quotePartCoAcheteurPct,setQuotePartCoAcheteurPct]=useState(100 - initialQpp);
  const current=STATUTS_COMMERCIAL.find(x=>x.key===(lot.statutCommercial||"LIBRE"))||STATUTS_COMMERCIAL[0];
  const AV_KEYS_HIDDEN=["AV10","AV40","AV70","AV95"];
  const nextStatuts=STATUTS_COMMERCIAL.filter(s=>s.key!==current.key&&!AV_KEYS_HIDDEN.includes(s.key));
  const inp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:6,color:"#1e293b",padding:"5px 8px",fontSize:12,width:"100%"};

  // Liste des clients CRM (pour le dropdown indivision) — exclut le client principal du lot.
  const crmClients=Array.isArray(crm&&crm.clients)?crm.clients:[];
  const eligibleCoAcheteurs=crmClients.filter(c=>c&&c.id&&c.id!==lot.clientId);
  const clientLabel=(c)=>`${(c.prenom||'').trim()} ${(c.nom||'').trim()}`.trim()||c.id;

  // Résolution des noms à afficher en label des quote-parts.
  const principalClient = crmClients.find(c=>c&&c.id===lot.clientId);
  const principalName   = principalClient
    ? clientLabel(principalClient)
    : (newClient || lot.clientNom || "Acheteur principal");
  const coAcheteurFull  = coAcheteurClientId
    ? crmClients.find(c=>c&&c.id===coAcheteurClientId)
    : null;
  const coAcheteurName  = coAcheteurFull ? clientLabel(coAcheteurFull) : "Co-acheteur";

  // Validations indivision : seulement quand statut OPTION + type indivision
  const showIndivisionDetails=newStatut==="OPTION"&&typeAcquisition==="indivision";
  const qppNum = Number(String(quotePartPrincipalPct).replace(',','.'));
  const qpcNum = Number(String(quotePartCoAcheteurPct).replace(',','.'));
  const qppValid = !isNaN(qppNum) && qppNum>0 && qppNum<100;
  const qpcValid = !isNaN(qpcNum) && qpcNum>0 && qpcNum<100;
  const sumOk    = qppValid && qpcValid && Math.abs(qppNum + qpcNum - 100) < 0.01;
  const indivisionInvalid = showIndivisionDetails && (
    !coAcheteurClientId
    || coAcheteurClientId===lot.clientId
    || !qppValid
    || !qpcValid
    || !sumOk
  );
  const canSave=!!newStatut && !indivisionInvalid;

  const handleSave=()=>{
    if(!canSave) return;
    // Construction des données indivision : on n'envoie le type que si on est en OPTION
    // (la modale ne modifie ce champ qu'au passage en OPTION, comme la case actuelle).
    // On ne stocke que quotePartPrincipalPct : le co-acheteur est dérivé (100-principal).
    const indivisionData=newStatut==="OPTION"
      ? {
          typeAcquisition,
          coAcheteurClientId: typeAcquisition==="indivision" ? coAcheteurClientId : "",
          quotePartPrincipalPct: typeAcquisition==="indivision" ? qppNum : 50,
        }
      : null;
    onSave(lotIdx,newStatut,newDate,newClient,newNote,indivisionData);
    onClose();
  };

  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.45)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:520,maxWidth:"95vw",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:16,marginBottom:4}}>Statut commercial — Lot {lotIdx+1}</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:14}}>Statut actuel : <span style={{background:current.bg,color:current.color,borderRadius:4,padding:"1px 6px",fontWeight:700}}>{current.label}</span></div>
        {(lot.historiqueStatut||[]).length>0&&(
          <div style={{marginBottom:14}}>
            <div style={{fontSize:11,fontWeight:700,color:"#334155",marginBottom:5}}>Historique</div>
            <div style={{maxHeight:130,overflowY:"auto",border:"1px solid #e2e8f0",borderRadius:8}}>
              {[...(lot.historiqueStatut||[])].reverse().map((h,i)=>{
                const from=STATUTS_COMMERCIAL.find(x=>x.key===h.from)||STATUTS_COMMERCIAL[0];
                const to=STATUTS_COMMERCIAL.find(x=>x.key===h.to)||STATUTS_COMMERCIAL[0];
                return <div key={i} style={{padding:"5px 10px",borderBottom:"1px solid #f1f5f9",fontSize:11}}>
                  <span style={{color:"#94a3b8"}}>{h.date}</span>{" · "}
                  <span style={{background:from.bg,color:from.color,borderRadius:3,padding:"0 4px",fontSize:10,fontWeight:700}}>{from.label}</span>
                  {" → "}<span style={{background:to.bg,color:to.color,borderRadius:3,padding:"0 4px",fontSize:10,fontWeight:700}}>{to.label}</span>
                  {h.clientNom&&<span style={{color:"#64748b",marginLeft:8}}>{h.clientNom}</span>}
                  {h.note&&<span style={{color:"#94a3b8",marginLeft:8,fontStyle:"italic"}}>{h.note}</span>}
                </div>;
              })}
            </div>
          </div>
        )}
        <div style={{background:"#f8fafc",borderRadius:8,padding:14,border:"1px solid #e2e8f0",marginBottom:14}}>
          <div style={{fontSize:11,fontWeight:700,color:"#334155",marginBottom:8}}>Nouveau statut</div>
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8}}>
            <div><div style={{fontSize:10,color:"#64748b",marginBottom:3}}>Statut</div>
              <select value={newStatut} onChange={e=>setNewStatut(e.target.value)} style={inp}>
                <option value="">— Choisir —</option>
                {nextStatuts.map(s=><option key={s.key} value={s.key}>{s.label}{s.key==="LIBRE"?" (abandon)":""}</option>)}
              </select></div>
            <div><div style={{fontSize:10,color:"#64748b",marginBottom:3}}>Date</div><input type="date" value={newDate} onChange={e=>setNewDate(e.target.value)} style={inp}/></div>
            <div><div style={{fontSize:10,color:"#64748b",marginBottom:3}}>Client</div><input value={newClient} onChange={e=>setNewClient(e.target.value)} placeholder="Nom du client" style={inp}/></div>
            <div><div style={{fontSize:10,color:"#64748b",marginBottom:3}}>Note</div><input value={newNote} onChange={e=>setNewNote(e.target.value)} placeholder="Note libre" style={inp}/></div>
          </div>
          {newStatut==="OPTION"&&(
            <div style={{marginTop:10,padding:"10px 12px",background:"#eff6ff",borderRadius:6,border:"1px solid #bfdbfe"}}>
              <div style={{fontSize:11,fontWeight:700,color:"#1d4ed8",marginBottom:6}}>Type d'acquisition</div>
              <select value={typeAcquisition} onChange={e=>setTypeAcquisition(e.target.value)} style={{...inp,marginBottom:showIndivisionDetails?10:0}}>
                <option value="seul">Acheteur seul</option>
                <option value="couple">Couple (1 foyer fiscal — co-acquéreur dans la fiche CRM)</option>
                <option value="indivision">Indivision (2 foyers fiscaux distincts — 2 factures)</option>
              </select>
              {showIndivisionDetails&&(
                <div>
                  {/* Choix du co-acheteur (pleine largeur) */}
                  <div style={{marginBottom:10}}>
                    <div style={{fontSize:10,color:"#64748b",marginBottom:3}}>Co-acheteur (CRM)</div>
                    <select value={coAcheteurClientId} onChange={e=>setCoAcheteurClientId(e.target.value)} style={inp}>
                      <option value="">— Choisir un client —</option>
                      {eligibleCoAcheteurs.map(c=>(
                        <option key={c.id} value={c.id}>{clientLabel(c)}</option>
                      ))}
                    </select>
                  </div>
                  {/* Quote-parts : un input par acheteur, label = nom de l'acheteur */}
                  <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8}}>
                    <div>
                      <div style={{fontSize:10,color:"#64748b",marginBottom:3}}>
                        Quote-part de <b>{principalName}</b> (%)
                      </div>
                      <input type="number" min="1" max="99" step="any" value={quotePartPrincipalPct}
                        onChange={e=>setQuotePartPrincipalPct(e.target.value)} style={inp}/>
                    </div>
                    <div>
                      <div style={{fontSize:10,color:"#64748b",marginBottom:3}}>
                        Quote-part de <b>{coAcheteurName}</b> (%)
                      </div>
                      <input type="number" min="1" max="99" step="any" value={quotePartCoAcheteurPct}
                        onChange={e=>setQuotePartCoAcheteurPct(e.target.value)} style={inp}/>
                    </div>
                  </div>
                  {/* Indicateur somme live (gris si OK, rouge sinon) */}
                  <div style={{marginTop:6,fontSize:11,color:sumOk?"#64748b":"#b91c1c"}}>
                    Somme : {(isNaN(qppNum)?0:qppNum) + (isNaN(qpcNum)?0:qpcNum)}%
                    {sumOk ? " ✓" : " — doit être exactement 100%"}
                  </div>
                  {indivisionInvalid&&(
                    <div style={{marginTop:6,fontSize:11,color:"#b91c1c"}}>
                      {!coAcheteurClientId
                        ? "⚠ Sélectionnez un co-acheteur."
                        : coAcheteurClientId===lot.clientId
                          ? "⚠ Le co-acheteur doit être distinct du client principal."
                          : (!qppValid || !qpcValid)
                            ? "⚠ Chaque quote-part doit être strictement entre 0 et 100."
                            : "⚠ La somme des deux quote-parts doit faire 100%."}
                    </div>
                  )}
                </div>
              )}
            </div>
          )}
        </div>
        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"7px 16px",fontSize:13,cursor:"pointer"}}>Fermer</button>
          <button onClick={handleSave} disabled={!canSave}
            style={{background:canSave?"#2563eb":"#94a3b8",color:"#fff",border:"none",borderRadius:8,padding:"7px 22px",fontSize:13,fontWeight:700,cursor:canSave?"pointer":"default"}}>
            Enregistrer
          </button>
        </div>
      </div>
    </div>
  );
}

const FICHE_INP={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"6px 10px",fontSize:13,width:"100%"};
const FICHE_CALC={background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,color:"#1d4ed8",padding:"6px 10px",fontSize:13,fontWeight:600};
const COUT_INP={background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,color:"#1e293b",padding:"4px 8px",fontSize:12,width:"100%",textAlign:"right"};
const COUT_CALC={background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6,color:"#1d4ed8",padding:"4px 8px",fontSize:12,textAlign:"right"};

/* ── CellDisplay : affichage cellule Plan Hebdo (clic => modale) ── */
function CellDisplay({value, statut, onClick}) {
  const base = statut==="R" ? C.green : C.blue;
  const col = value!=null ? (value>0 ? base : statut==="R" ? "#f87171" : "#818cf8") : "#cbd5e1";
  return (
    <div onClick={onClick}
      style={{minWidth:76,cursor:"pointer",textAlign:"right",fontSize:11,color:col,
              padding:"2px 4px",userSelect:"none",borderRadius:4,transition:"background .1s"}}
      title="Cliquer pour modifier"
      onMouseEnter={e=>e.currentTarget.style.background="#f1f5f9"}
      onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
      {value!=null ? (value===0 ? <span style={{color:"#94a3b8",fontSize:9}}>0</span> : fmtEur(value,true)) : "·"}
    </div>
  );
}

/* ── CellEditModal : fenêtre d'édition d'une cellule ── */
function CellEditModal({row, weekIso, currentValue, allValues, onSave, onClose}) {
  const [input, setInput] = useState(currentValue!=null ? String(currentValue) : "");
  const [redistWeek, setRedistWeek] = useState("");
  const inputRef = useRef();
  useEffect(()=>{ if(inputRef.current){inputRef.current.focus();inputRef.current.select();} },[]);

  const rowVals = allValues.filter(v=>v.rowId===row.id);
  const rowTotal = rowVals.reduce((s,v)=>s+v.montant, 0);
  const remainingFromHere = rowVals.filter(v=>v.weekIso>=weekIso).reduce((s,v)=>s+v.montant, 0);
  const futureVals = rowVals.filter(v=>v.weekIso>weekIso).sort((a,b)=>a.weekIso.localeCompare(b.weekIso));

  const parsedNew = parseFloat(String(input).replace(",",".").replace(/\s/g,""));
  const newVal = isNaN(parsedNew) ? null : parsedNew;
  const diff = (newVal||0) - (currentValue||0);
  const showRedist = diff!==0 && futureVals.length>0;

  const handleSave = () => {
    onSave(newVal, redistWeek ? {weekIso:redistWeek, adjustment:-diff} : null);
    onClose();
  };

  const ss = STATUT_STYLE[row.statut]||STATUT_STYLE.R;
  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.45)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}}
         onClick={onClose}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:440,maxWidth:"95vw",boxShadow:"0 8px 40px #0003",border:"1px solid "+C.border}}
           onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:16,marginBottom:4}}>Modifier la valeur</div>
        <div style={{fontSize:12,color:C.muted,marginBottom:16,display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"}}>
          <span style={{background:ss.bg,color:ss.color,borderRadius:3,padding:"0 6px",fontSize:10,fontWeight:700}}>{row.statut}</span>
          <span>{row.label}</span>
          <span style={{color:C.border}}>—</span>
          <span>semaine du {fmtW(weekIso)}</span>
        </div>
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8,marginBottom:16,background:"#f8fafc",borderRadius:8,padding:10,border:"1px solid "+C.border}}>
          <div>
            <div style={{fontSize:10,color:C.muted,marginBottom:2}}>Total de la ligne</div>
            <div style={{fontWeight:700,fontSize:13,color:C.text}}>{fmtEur(rowTotal)}</div>
          </div>
          <div>
            <div style={{fontSize:10,color:C.muted,marginBottom:2}}>Restant à partir de cette date</div>
            <div style={{fontWeight:700,fontSize:13,color:C.text}}>{fmtEur(remainingFromHere)}</div>
          </div>
        </div>
        <div style={{marginBottom:16}}>
          <div style={{fontSize:11,color:C.muted,marginBottom:4}}>Nouvelle valeur (€) — laisser vide pour effacer</div>
          <input ref={inputRef} value={input} onChange={e=>setInput(e.target.value)}
            onKeyDown={e=>{if(e.key==="Enter")handleSave();if(e.key==="Escape")onClose();}}
            placeholder="Ex : 35000"
            style={{...FICHE_INP,fontWeight:600,fontSize:15}}/>
        </div>
        {showRedist && (
          <div style={{background:"#fffbeb",borderRadius:8,border:"1px solid #fde68a",padding:12,marginBottom:16}}>
            <div style={{fontSize:12,fontWeight:700,color:"#b45309",marginBottom:4}}>
              ⚖️ Différence : {diff>0?"+":""}{fmtEur(diff,true)}
            </div>
            <div style={{fontSize:11,color:"#92400e",marginBottom:8}}>
              Pour maintenir le total de la ligne, souhaitez-vous reporter {fmtEur(Math.abs(diff),true)} sur un flux futur ?
            </div>
            <select value={redistWeek} onChange={e=>setRedistWeek(e.target.value)}
              style={{background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,color:"#92400e",padding:"5px 8px",fontSize:12,width:"100%"}}>
              <option value="">— Ne pas redistribuer —</option>
              {futureVals.map(v=>(
                <option key={v.weekIso} value={v.weekIso}>
                  {fmtW(v.weekIso)} : {fmtEur(v.montant)} → {fmtEur(v.montant+(-diff))}
                </option>
              ))}
            </select>
          </div>
        )}
        <div style={{display:"flex",gap:8,justifyContent:"flex-end",marginTop:8}}>
          <button onClick={onClose}
            style={{background:"#f1f5f9",border:"1px solid "+C.border,borderRadius:8,padding:"7px 16px",fontSize:13,cursor:"pointer",color:C.text}}>
            Annuler
          </button>
          <button onClick={handleSave}
            style={{background:C.accent,color:"#fff",border:"none",borderRadius:8,padding:"7px 22px",fontSize:13,fontWeight:700,cursor:"pointer"}}>
            Enregistrer
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── CoutRow : ligne coûts avec support autoReal + OverrideCell ── */
function CoutRow({label,fieldP,fieldR,fiche,onUpdate,calcP,calcR,isPct,autoReal}) {
  const vP = calcP!==undefined ? calcP : toN(fiche[fieldP]);
  const hasOverride = fieldR && fiche[fieldR]!=="" && fiche[fieldR]!==undefined && fiche[fieldR]!==null;
  const vR = calcR!==undefined ? calcR
    : hasOverride ? toN(fiche[fieldR])
    : autoReal!==undefined ? autoReal
    : toN(fiche[fieldR]||"");
  const ecart = vR-vP;
  const hasVal = vP||vR;
  const ecartColor = isPct?"#94a3b8":ecart>0?"#dc2626":ecart<0?"#16a34a":"#94a3b8";
  const ecartLabel = isPct?"—":hasVal?(ecart>=0?"+":"")+fmtEur(ecart,true):"—";
  const btn={background:"none",border:"none",cursor:"pointer",fontSize:13,padding:"0 2px",lineHeight:1};
  const li={border:"1px solid",borderRadius:6,padding:"4px 6px",fontSize:12,width:88,textAlign:"right",outline:"none"};
  return (
    <tr style={{borderBottom:"1px solid #f1f5f9"}}>
      <td style={{padding:"5px 8px",fontSize:12,color:C.muted,whiteSpace:"nowrap",minWidth:160}}>{label}</td>
      <td style={{padding:"3px 4px",minWidth:130}}>
        {calcP!==undefined
          ?<div style={COUT_CALC}>{fmtEur(calcP)}</div>
          :<input value={fiche[fieldP]||""} onChange={e=>onUpdate(fieldP,e.target.value)} style={COUT_INP}/>}
      </td>
      <td style={{padding:"3px 4px",minWidth:130}}>
        {calcR!==undefined ? (
          <div style={COUT_CALC}>{fmtEur(calcR)}</div>
        ) : autoReal!==undefined ? (
          <div style={{display:"flex",alignItems:"center",gap:2,justifyContent:"flex-end"}}>
            {hasOverride ? (
              <>
                <input value={fiche[fieldR]} onChange={e=>onUpdate(fieldR,e.target.value)}
                  style={{...li,background:"#fef9c3",borderColor:"#f59e0b",color:"#92400e"}}/>
                <button onClick={()=>onUpdate(fieldR,"")} title="Rétablir depuis Plan Hebdo" style={{...btn,color:"#f59e0b"}}>↺</button>
              </>
            ) : (
              <>
                <div style={{...COUT_CALC,padding:"4px 6px",fontSize:12,minWidth:80,textAlign:"right"}}>
                  {(autoReal>0||autoReal<0) ? fmtEur(autoReal) : "—"}
                </div>
                <button onClick={()=>onUpdate(fieldR,(autoReal>0||autoReal<0)?String(Math.round(autoReal)):"")}
                  title="Forcer la valeur" style={{...btn,color:"#94a3b8"}}>✎</button>
              </>
            )}
          </div>
        ) : (
          <input value={fiche[fieldR]||""} onChange={e=>onUpdate(fieldR,e.target.value)} style={COUT_INP}/>
        )}
      </td>
      <td style={{padding:"5px 8px",minWidth:90,textAlign:"right",fontSize:11,fontWeight:600,color:ecartColor}}>{ecartLabel}</td>
    </tr>
  );
}

function FicheField({label,field,type,opts,calc,calcVal,suffix,fiche,onUpdate}) {
  return (
    <div style={{marginBottom:10}}>
      <div style={{fontSize:11,color:C.muted,marginBottom:3}}>{label}{calc&&<span style={{color:C.accent,fontSize:10,marginLeft:4}}>calculé</span>}</div>
      {calc?<div style={FICHE_CALC}>{calcVal}</div>
      :opts?<select value={fiche[field]||""} onChange={e=>onUpdate(field,e.target.value)} style={FICHE_INP}>{opts.map(o=><option key={o}>{o}</option>)}</select>
      :<div style={{display:"flex",alignItems:"center",gap:4}}>
        <input type={type||"text"} value={fiche[field]||""} onChange={e=>onUpdate(field,e.target.value)} style={FICHE_INP}/>
        {suffix&&<span style={{fontSize:13,color:C.muted,flexShrink:0}}>{suffix}</span>}
      </div>}
    </div>
  );
}

function OverrideCell({calcVal, overrideVal, onSet, onClear, onBlur}) {
  const has=overrideVal!==""&&overrideVal!==undefined&&overrideVal!==null;
  const li={border:"1px solid",borderRadius:6,padding:"4px 6px",fontSize:12,width:88,textAlign:"right",outline:"none"};
  const btn={background:"none",border:"none",cursor:"pointer",fontSize:13,padding:"0 2px",lineHeight:1};
  return (
    <div style={{display:"flex",alignItems:"center",gap:2,justifyContent:"flex-end"}}>
      {has?(
        <>
          <input value={overrideVal} onChange={e=>onSet(e.target.value)} onBlur={onBlur}
            style={{...li,background:"#fef9c3",borderColor:"#f59e0b",color:"#92400e"}}/>
          <button onClick={onClear} title="Rétablir le calcul" style={{...btn,color:"#f59e0b"}}>↺</button>
        </>
      ):(
        <>
          <div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,minWidth:80,textAlign:"right"}}>{calcVal>0?fmtEur(calcVal):"—"}</div>
          <button onClick={()=>onSet(calcVal>0?String(Math.round(calcVal)):"")} title="Forcer la valeur" style={{...btn,color:"#94a3b8"}}>✎</button>
        </>
      )}
    </div>
  );
}

function FicheSection({title,color,children}) {
  return (
    <div style={{background:C.card,borderRadius:12,border:"1px solid "+C.border,padding:18,marginBottom:14}}>
      <div style={{fontWeight:700,marginBottom:14,color:color||C.accent,fontSize:14}}>{title}</div>
      {children}
    </div>
  );
}

/* ── STATUT_GROUPES : utilisé par FichesProgrammes ET PlanTresorerie ── */
const STATUT_GROUPES = [
  {key:"En cours",   label:"En cours",   dot:"#16a34a", bg:"#dcfce7", color:"#15803d"},
  {key:"Livré",      label:"Livré",      dot:"#2563eb", bg:"#dbeafe", color:"#1d4ed8"},
  {key:"Abandonné",  label:"Abandonné",  dot:"#9ca3af", bg:"#f1f5f9", color:"#64748b"},
];

/* ── Helper : <option>s groupés par statut, triés alpha intra-groupe ──
   Filtre les programmes globaux (p.isGlobal). Ordre des groupes piloté par
   STATUT_GROUPES. Le label de chaque <option> est "ville – nom" si ville
   présente, sinon nom seul. Utilisé par les dropdowns programmes de l'app
   (Reception, FichesProgrammes, PlanTresorerie, Journal).
   Retourne un fragment, à imbriquer dans un <select>. */
function GroupedProgrammeOptions({projects, fiches}) {
  const labelOf = p => (p.ville?p.ville+" – ":"")+(p.nom||"");
  // Source du statut : fiche (édité dans FichesProgrammes) sinon p.statut.
  const statutOf = p => (fiches && fiches[p.id] && fiches[p.id].statut) || p.statut || "En cours";
  const filtered = (projects||[]).filter(p => !p.isGlobal);
  return STATUT_GROUPES.map(sg => {
    const bucket = filtered
      .filter(p => statutOf(p) === sg.key)
      .sort((a,b) => labelOf(a).localeCompare(labelOf(b), "fr", {sensitivity:"base"}));
    if (bucket.length === 0) return null;
    return (
      <optgroup key={sg.key} label={sg.label}>
        {bucket.map(p => <option key={p.id} value={p.id}>{labelOf(p)}</option>)}
      </optgroup>
    );
  });
}
