// ─────────────────────────────────────────────────────────────
// DoricMatch AI — src/components/dashboard.jsx
// Tableau de bord et historique des matchings.
// Chargé via <script type="text/babel"> dans index.html (ordre important).
// ─────────────────────────────────────────────────────────────

function Dashboard({ go, investors, enseignes, onEnrich, onCriterion, onEnsEnrich, onEnsCriterion, onAutoEnrich, onAutoEnrichEns, onPatchFiche, onOpen, onOpenEns, onMoveContact, onAddContact, onCreateCompanyMove }) {
  const [base, setBase] = useState("investors"); // base ciblée par l'enrichissement IA
  const [running, setRunning] = useState(false);
  const [results, setResults] = useState([]); // { id, name, found, criteria, base }
  const [err, setErr] = useState("");
  const [progress, setProgress] = useState("");
  const [enrichDone, setEnrichDone] = useState(0);
  const [enrichTotal, setEnrichTotal] = useState(0);
  const [elapsed, setElapsed] = useState(0);
  const elapsedRef = useRef(null);
  const [enrichSel, setEnrichSel] = useState([]);
  // #6 — complétion des coordonnées manquantes
  const [fillRunning, setFillRunning] = useState(false);
  const [fillProgress, setFillProgress] = useState("");
  const [fillDone, setFillDone] = useState(0);
  const [fillTotal, setFillTotal] = useState(0);
  const [fillMsg, setFillMsg] = useState("");

  const baseList = base === "investors" ? investors : enseignes;
  useEffect(() => { setEnrichSel([]); setResults([]); setErr(""); setFillMsg(""); }, [base]);
  const toggleEnrich = (id) => setEnrichSel(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]);
  const allSelected = baseList.length > 0 && enrichSel.length === baseList.length;
  const toggleAll = () => setEnrichSel(allSelected ? [] : baseList.map(i => i.id));

  const FIELD_LABELS = { typologies: "Typologie d'actif", city: "Localisation", price: "Prix", yield: "Rendement", tenant: "Type de locataire", strategy: "Stratégie", other: "Information" };
  const CRIT_LABELS = { typologie: "Typologie à ajouter", tenant: "Locataire recherché", region: "Région ciblée", strategy: "Stratégie", yieldMin: "Rendement min", budgetMax: "Budget max", walbMin: "WALB min", monovalent: "Monovalent" };

  const runEnrichment = async () => {
    const targets = baseList.filter(i => enrichSel.includes(i.id));
    if (!targets.length) { setErr(`Sélectionnez au moins ${base === "investors" ? "un investisseur" : "une enseigne"} à enrichir.`); return; }
    if (!confirmCost(`Enrichissement IA de ${targets.length} fiche(s) (0,20 €/fiche).`, targets.length * IA_COSTS.enrich)) return;
    setRunning(true); setErr(""); setResults([]);
    setEnrichDone(0); setEnrichTotal(targets.length); setElapsed(0);
    const t0 = Date.now();
    elapsedRef.current = setInterval(() => setElapsed(Math.floor((Date.now() - t0) / 1000)), 250);
    const out = [];
    try {
      for (const it of targets) {
        setProgress(`Recherche sur ${it.name}…`);
        let sys, userMsg;
        if (base === "investors") {
          sys = `Tu es un analyste qui ENRICHIT la fiche d'un investisseur immobilier à partir de sources WEB, et en déduit des critères d'achat. Pour la société donnée, recherche sur le web en priorité dans : (1) ses COMMUNIQUÉS DE PRESSE et communiqués corporate, (2) sa PAGE LINKEDIN société, (3) son SITE WEB officiel, (4) la presse spécialisée immobilière. Concentre-toi sur ses actualités récentes : acquisitions, cessions, levées de fonds, ouvertures, stratégie et critères d'investissement annoncés (typologies, zones, rendement, budget, locataires). Renvoie UNIQUEMENT un JSON :
{"found":[{"field":"typologies|city|price|yield|tenant|strategy|other","value":"texte court","source":"Communiqué de presse|Communiqué corporate|LinkedIn société|Site web|Presse spécialisée","url":"lien si dispo sinon vide","confidence":0-100}],
"criteria":[{"field":"typologie|tenant|region|strategy|yieldMin|budgetMax|walbMin|monovalent","value":"valeur précise (ex: 'Logistique', 'Île-de-France', '6.5', '15000000', 'oui')","label":"libellé court","confidence":0-100}]}
"found" = informations/actualités qui ENRICHISSENT la fiche client. "criteria" = critères d'achat STRUCTURÉS déduits, qui AFFINERONT le matching (typologie/region = un mot, yieldMin/walbMin = nombre, budgetMax = nombre en euros, monovalent = oui/non). Max 4 found + 4 criteria. N'invente rien : si une information n'est pas trouvée sur ces sources, ne la mets pas. Si rien de fiable, listes vides.`;
          userMsg = `Société à enrichir : ${it.name} (${it.type}), basée à ${it.city || "n.c."}.\nSite web officiel : ${it.website || "n.c."}\nPage LinkedIn société : ${it.companyLinkedin || "n.c."}\nCommence par consulter ces deux sources, puis élargis aux communiqués de presse et à la presse spécialisée.\nCritères actuels : rendement min ${it.yieldMin || "n.c."} %, budget max ${it.budgetMax || "n.c."}, typologies ${(it.typologies || []).join(", ") || "—"}.`;
        } else {
          sys = `Tu es un analyste en immobilier commercial qui ENRICHIT la fiche d'une ENSEIGNE (preneur à bail) à partir de sources WEB, et en déduit son cahier des charges d'implantation. Recherche en priorité : (1) ses COMMUNIQUÉS DE PRESSE et plan de développement / expansion, (2) sa PAGE LINKEDIN, (3) son SITE WEB officiel (rubrique implantation / franchise / nous rejoindre), (4) la presse spécialisée retail. Concentre-toi sur : nouvelles ouvertures, villes ciblées, surfaces recherchées, types d'emplacements (centre-ville, retail park, centre commercial, bureaux…), budget loyer, nombre d'ouvertures prévues. Renvoie UNIQUEMENT un JSON :
{"found":[{"field":"secteur|emplacement|surface|loyer|zone|other","value":"texte court","source":"Communiqué de presse|Plan de développement|LinkedIn|Site web|Presse spécialisée","url":"lien si dispo sinon vide","confidence":0-100}],
"criteria":[{"field":"localType|sector|region|surfaceMin|surfaceMax|rentMax|frontageMin|fluxMin","value":"valeur précise (ex: 'Retail park', 'Île-de-France', '120', '350', '450')","label":"libellé court","confidence":0-100}]}
"found" = actualités qui enrichissent la fiche. "criteria" = critères d'implantation STRUCTURÉS (localType = un type de local, region = une région, surfaceMin/surfaceMax/rentMax/frontageMin/fluxMin = un nombre). Max 4 found + 4 criteria. N'invente rien.`;
          userMsg = `Enseigne à enrichir : ${it.name} (${it.sector || it.type}), siège ${it.city || "n.c."}.\nSite web : ${it.website || "n.c."}\nLinkedIn : ${it.companyLinkedin || "n.c."}\nCommence par ces sources, puis élargis aux communiqués et à la presse retail.\nCritères actuels : surface ${it.surfaceMin || "?"}–${it.surfaceMax || "?"} m², loyer max ${it.rentMax || "?"} €/m²/an, types ${(it.localTypes || []).join(", ") || "—"}.`;
        }
        let data;
        try {
          data = await callClaude({ model: AI_MODELS.standard, max_tokens: 1100, tools: webTools(6, true), system: sys, messages: [{ role: "user", content: userMsg }] });
        } catch (e) {
          if (e.isAuth) { setErr("IA indisponible : secret ANTHROPIC_API_KEY manquant ou invalide côté serveur (Supabase)."); setRunning(false); return; }
          continue;
        }
        const txt = data.content.filter(b => b.type === "text").map(b => b.text).join("").replace(/```json|```/g, "").trim();
        let parsed = { found: [], criteria: [] };
        try { parsed = JSON.parse(txt.slice(txt.indexOf("{"), txt.lastIndexOf("}") + 1)); } catch (_) {}
        // Les critères et infos trouvés par l'IA sont APPLIQUÉS AUTOMATIQUEMENT (plus de clic) :
        // les champs de la fiche se remplissent seuls et sont persistés dans Supabase.
        const found = (parsed.found || []).map(f => ({ ...f, applied: true }));
        const criteria = (parsed.criteria || []).map(c => ({ ...c, applied: true }));
        if (found.length || criteria.length) {
          const now = new Date().toLocaleDateString("fr-FR");
          const fLabels = base === "investors" ? FIELD_LABELS : ENS_FIELD_LABELS;
          const cLabels = base === "investors" ? CRIT_LABELS : ENS_CRIT_LABELS;
          const entries = [
            ...found.map(f => ({ field: f.field, label: fLabels[f.field] || "Information", value: f.value, source: f.source, url: f.url, confidence: f.confidence, date: now })),
            ...criteria.map(c => ({ field: "other", label: "Critère affiné", value: (c.label || cLabels[c.field] || c.field) + " : " + c.value, source: "Enrichissement IA", url: "", confidence: c.confidence || 85, date: now })),
          ];
          (base === "investors" ? onAutoEnrich : onAutoEnrichEns)(it.id, entries, criteria);
          out.push({ id: it.id, name: it.name, found, criteria, base });
        }
        setEnrichDone(d => d + 1);
      }
      setResults(out);
      if (!out.length) setErr("Aucune actualité fiable trouvée pour le moment.");
    } finally { setRunning(false); setProgress(""); if (elapsedRef.current) clearInterval(elapsedRef.current); }
  };

  // #6 — Compléter les coordonnées manquantes (site, tél., contact) via recherche web IA.
  const runFillContacts = async () => {
    const targets = baseList.filter(f => enrichSel.includes(f.id) && (!f.email || !f.phone || !f.contact));
    if (!targets.length) { setErr("Sélectionnez au moins une fiche avec des coordonnées manquantes."); return; }
    setFillRunning(true); setErr(""); setFillMsg(""); setFillDone(0); setFillTotal(targets.length);
    let ok = 0;
    for (const f of targets) {
      setFillProgress(`Recherche des coordonnées de ${f.name}…`);
      const sys = `Tu recherches sur le WEB les coordonnées professionnelles PUBLIQUES d'une société et de son contact décideur. Renvoie UNIQUEMENT un JSON (sans backticks) : {"website":"","companyPhone":"","contactName":"","role":"","email":"","phone":"","linkedin":""}. N'invente rien : laisse vide si non trouvé avec certitude.`;
      const userMsg = `Société : ${f.name}${f.city ? ", " + f.city : ""}${f.type ? " (" + f.type + ")" : ""}. Site connu : ${f.website || "n.c."}. Trouve : site web officiel, téléphone standard, et si possible un contact dirigeant/décideur (nom, fonction, email professionnel, téléphone, URL LinkedIn).`;
      let data;
      try { data = await callClaude({ model: AI_MODELS.standard, max_tokens: 500, tools: webTools(5, true), system: sys, messages: [{ role: "user", content: userMsg }] }); }
      catch (e) { if (e.isAuth) { setErr("IA indisponible : secret ANTHROPIC_API_KEY manquant ou invalide côté serveur (Supabase)."); setFillRunning(false); setFillProgress(""); return; } setFillDone(d => d + 1); continue; }
      const txt = data.content.filter(b => b.type === "text").map(b => b.text).join("").replace(/```json|```/g, "").trim();
      let p = {}; try { p = JSON.parse(txt.slice(txt.indexOf("{"), txt.lastIndexOf("}") + 1)); } catch (_) {}
      const cs = (f.contacts && f.contacts.length) ? f.contacts.map(c => ({ ...c })) : [{ name: f.contact || "", role: f.role || "", email: f.email || "", phone: f.phone || "", linkedin: f.linkedin || "" }];
      const c0 = cs[0];
      if (!c0.name && p.contactName) c0.name = p.contactName;
      if (!c0.role && p.role) c0.role = p.role;
      if (!c0.email && p.email) c0.email = p.email;
      if (!c0.phone && p.phone) c0.phone = p.phone;
      if (!c0.linkedin && p.linkedin) c0.linkedin = p.linkedin;
      const patch = { contacts: cs };
      if (!f.website && p.website) patch.website = p.website;
      if (!f.companyPhone && p.companyPhone) patch.companyPhone = p.companyPhone;
      if (!f.contact && c0.name) patch.contact = c0.name;
      if (!f.email && c0.email) patch.email = c0.email;
      if (!f.phone && c0.phone) patch.phone = c0.phone;
      const filled = (patch.website || patch.companyPhone || patch.contact || patch.email || patch.phone || c0.linkedin);
      if (filled && onPatchFiche) { await onPatchFiche(base, f.id, patch); ok++; }
      setFillDone(d => d + 1);
    }
    setFillProgress(""); setFillRunning(false);
    setFillMsg(`${ok} fiche(s) complétée(s) sur ${targets.length} traitée(s).`);
  };

  const applyOne = (ri, fi) => {
    setResults(rs => rs.map((r, i) => i === ri ? { ...r, found: r.found.map((f, j) => j === fi ? { ...f, applied: true } : f) } : r));
    const r = results[ri], f = r.found[fi];
    const labels = r.base === "investors" ? FIELD_LABELS : ENS_FIELD_LABELS;
    const entry = { field: f.field, label: labels[f.field] || "Information", value: f.value, source: f.source, url: f.url, confidence: f.confidence, date: new Date().toLocaleDateString("fr-FR") };
    (r.base === "investors" ? onEnrich : onEnsEnrich)(r.id, entry);
  };
  const applyCrit = (ri, ci) => {
    setResults(rs => rs.map((r, i) => i === ri ? { ...r, criteria: r.criteria.map((c, j) => j === ci ? { ...c, applied: true } : c) } : r));
    const r = results[ri], c = r.criteria[ci];
    const labels = r.base === "investors" ? CRIT_LABELS : ENS_CRIT_LABELS;
    const crit = { field: c.field, label: c.label || labels[c.field] || "Critère", value: c.value, source: "Enrichissement IA", confidence: c.confidence };
    (r.base === "investors" ? onCriterion : onEnsCriterion)(r.id, crit);
  };
  const openFiche = (r) => (r.base === "investors" ? onOpen : onOpenEns)(r.id);

  // ── Suivi LinkedIn des contacts : changement de poste / de société ──
  const [ctcRunning, setCtcRunning] = useState(false);
  const [ctcErr, setCtcErr] = useState("");
  const [ctcProgress, setCtcProgress] = useState("");
  const [ctcDone, setCtcDone] = useState(0);
  const [ctcTotal, setCtcTotal] = useState(0);
  const [ctcResults, setCtcResults] = useState([]);
  const [ctcSel, setCtcSel] = useState([]);
  useEffect(() => { setCtcSel([]); setCtcResults([]); setCtcErr(""); }, [base]);
  const ctcToggle = (id) => setCtcSel(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]);
  const ctcAllSel = baseList.length > 0 && ctcSel.length === baseList.length;
  const ctcToggleAll = () => setCtcSel(ctcAllSel ? [] : baseList.map(i => i.id));
  const ctcCount = baseList.filter(i => ctcSel.includes(i.id)).reduce((n, f) => n + (f.contacts || []).filter(c => (c.name || "").trim()).length, 0);

  const norm = (s) => String(s || "").toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]/g, "");
  const findCompanyByName = (name) => {
    const n = norm(name); if (!n) return null;
    return baseList.find(c => { const cn = norm(c.name); return cn && (cn === n || cn.includes(n) || n.includes(cn)); }) || null;
  };
  const CTC_STATUS = {
    inchange: { label: "Inchangé", color: C.emerald },
    poste_change: { label: "Changement de poste", color: C.amber },
    societe_change: { label: "Changement de société", color: C.violet },
    parti: { label: "A quitté le poste", color: C.rose },
  };

  const runContactCheck = async () => {
    const targets = baseList.filter(i => ctcSel.includes(i.id));
    const jobs = [];
    targets.forEach(f => (f.contacts || []).forEach((c, ci) => { if ((c.name || "").trim()) jobs.push({ fiche: f, contact: c, ci }); }));
    if (!jobs.length) { setCtcErr("Sélectionnez au moins une fiche contenant un contact nommé."); return; }
    if (!confirmCost(`Vérification LinkedIn de ${jobs.length} contact(s) (0,20 €/contact).`, jobs.length * IA_COSTS.enrich)) return;
    setCtcRunning(true); setCtcErr(""); setCtcResults([]); setCtcDone(0); setCtcTotal(jobs.length); setCtcProgress("");
    const companiesList = baseList.map(c => c.name).filter(Boolean).join(" · ");
    const out = [];
    try {
      for (const job of jobs) {
        const f = job.fiche, c = job.contact;
        setCtcProgress(`Vérification de ${c.name}…`);
        const sys = `Tu es un analyste qui vérifie sur le WEB la situation professionnelle ACTUELLE d'un contact (poste + employeur), en priorité via son profil LinkedIn, puis les communiqués et la presse. Détermine s'il occupe TOUJOURS le même poste dans la même société, ou s'il a changé de POSTE et/ou de SOCIÉTÉ. En cas de changement, identifie la NOUVELLE société et le NOUVEAU poste. Si la personne a quitté son poste, cherche QUI la remplace au même poste dans l'ANCIENNE société. Renvoie UNIQUEMENT un JSON (sans backticks) :
{"status":"inchange|poste_change|societe_change|parti","message":"1-2 phrases de synthèse","newRole":"nouveau poste si changé, sinon vide","newCompany":"nouvelle société si changé d'employeur, sinon vide","newCompanyType":"si newCompany renseigné, son type (ex: Family office, Fonds d'investissement, Foncière, SCPI, Promoteur, Enseigne de retail, Groupe industriel…), sinon vide","newCompanyCity":"ville du siège de la nouvelle société si connue, sinon vide","newLinkedin":"URL LinkedIn du contact si trouvée, sinon vide","replacement":{"name":"personne qui le remplace à l'ancien poste, sinon vide","role":"poste","linkedin":"URL si trouvée, sinon vide","email":"","phone":""}}
N'invente rien : champ vide si non trouvé. "inchange" si aucun changement fiable.`;
        const userMsg = `Contact à vérifier : ${c.name}${c.role ? ", " + c.role : ""} chez ${f.name}.\nLinkedIn connu : ${c.linkedin || "n.c."}\nSociétés déjà présentes dans notre base (pour reconnaître une nouvelle société) : ${companiesList || "—"}.`;
        let data;
        try { data = await callClaude({ model: AI_MODELS.standard, max_tokens: 700, tools: webTools(5, true), system: sys, messages: [{ role: "user", content: userMsg }] }); }
        catch (e) { if (e.isAuth) { setCtcErr("IA indisponible : secret ANTHROPIC_API_KEY manquant ou invalide côté serveur (Supabase)."); setCtcRunning(false); return; } setCtcDone(d => d + 1); continue; }
        const txt = data.content.filter(b => b.type === "text").map(b => b.text).join("").replace(/```json|```/g, "").trim();
        let p = {};
        try { p = JSON.parse(txt.slice(txt.indexOf("{"), txt.lastIndexOf("}") + 1)); } catch (_) { p = { status: "inchange", message: txt.slice(0, 180) }; }
        const mc = p.newCompany ? findCompanyByName(p.newCompany) : null;
        out.push({
          key: f.id + "_" + job.ci, ficheId: f.id, ficheName: f.name, base,
          contact: c, contactName: c.name,
          status: p.status || "inchange", message: p.message || "",
          newRole: p.newRole || "", newCompany: p.newCompany || "", newCompanyType: p.newCompanyType || "",
          newCompanyCity: p.newCompanyCity || "", newLinkedin: p.newLinkedin || "",
          matchCompany: (mc && mc.id !== f.id) ? { id: mc.id, name: mc.name } : null,
          replacement: (p.replacement && p.replacement.name) ? p.replacement : null,
          done: { move: false, create: false, add: false },
        });
        setCtcDone(d => d + 1);
      }
      setCtcResults(out);
      if (!out.length) setCtcErr("Aucun contact vérifiable.");
    } finally { setCtcRunning(false); setCtcProgress(""); }
  };

  const ctcSetDone = (key, k) => setCtcResults(rs => rs.map(x => x.key === key ? { ...x, done: { ...x.done, [k]: true } } : x));
  const doMove = async (r) => { const contact = { ...r.contact }; if (r.newLinkedin) contact.linkedin = r.newLinkedin; await onMoveContact({ base: r.base, fromId: r.ficheId, toId: r.matchCompany.id, contact, newRole: r.newRole }); ctcSetDone(r.key, "move"); };
  const doCreateMove = async (r) => { const contact = { ...r.contact }; if (r.newLinkedin) contact.linkedin = r.newLinkedin; await onCreateCompanyMove({ base: r.base, name: r.newCompany, type: r.newCompanyType, fromId: r.ficheId, contact, newRole: r.newRole, city: r.newCompanyCity }); ctcSetDone(r.key, "create"); };
  const doAddReplacement = async (r) => { await onAddContact({ base: r.base, ficheId: r.ficheId, contact: { name: r.replacement.name, role: r.replacement.role || r.contact.role || "", email: r.replacement.email || "", phone: r.replacement.phone || "", linkedin: r.replacement.linkedin || "" } }); ctcSetDone(r.key, "add"); };
  const ctcChanges = ctcResults.filter(r => r.status !== "inchange").length;

  // ── Métriques unifiées ───────────────────────────────────────
  const invIncomplete = investors.filter(i => !i.email || !i.phone || !i.contact).length;
  const ensIncomplete = enseignes.filter(e => !e.email || !e.phone || !e.contact).length;
  const incomplete = invIncomplete + ensIncomplete;
  const enriched = investors.filter(i => (i.enrichments || []).length > 0).length + enseignes.filter(e => (e.enrichments || []).length > 0).length;
  const actifs = investors.filter(i => i.status === "Actif").length + enseignes.filter(e => e.status === "Actif").length;

  const kpis = [
    ["Investisseurs", investors.length, C.violet, "investors"],
    ["Enseignes", enseignes.length, C.indigo, "enseignes"],
    ["Mandats actifs", actifs, C.emerald, null],
    ["Coordonnées incomplètes", incomplete, incomplete ? C.amber : C.emerald, null],
  ];

  const invMix = [
    ["Family offices", investors.filter(i => i.type === "Family office").length, C.blue],
    ["Fonds / SCPI", investors.filter(i => /Fonds|SCPI/.test(i.type)).length, C.cyan],
    ["Foncières", investors.filter(i => /Foncière|Fonciere/.test(i.type)).length, C.violet],
    ["Privés", investors.filter(i => i.type === "Investisseur privé").length, C.rose],
  ];
  const maxInvMix = Math.max(1, ...invMix.map(m => m[1]));

  const ensSectorCount = {};
  enseignes.forEach(e => { const k = e.sector || "Autre"; ensSectorCount[k] = (ensSectorCount[k] || 0) + 1; });
  const ensColors = [C.emerald, C.indigo, C.amber, C.cyan, C.rose];
  const ensMix = Object.entries(ensSectorCount).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([l, v], i) => [l, v, ensColors[i % ensColors.length]]);
  const maxEnsMix = Math.max(1, ...ensMix.map(m => m[1]));

  const parseFr = (s) => { const m = /(\d{2})\/(\d{2})\/(\d{4})/.exec(s || ""); return m ? new Date(+m[3], +m[2] - 1, +m[1]).getTime() : 0; };
  const history = [
    ...investors.flatMap(i => (i.enrichments || []).map(e => ({ ...e, refId: i.id, refName: i.name, base: "investors" }))),
    ...enseignes.flatMap(e => (e.enrichments || []).map(x => ({ ...x, refId: e.id, refName: e.name, base: "enseignes" }))),
  ].sort((a, b) => parseFr(b.date) - parseFr(a.date));

  // ── #5 Relances échues (investisseurs + enseignes) ──
  const parseAnyDate = (s) => { if (!s) return 0; let m = /(\d{4})-(\d{2})-(\d{2})/.exec(s); if (m) return new Date(+m[1], +m[2] - 1, +m[3]).getTime(); m = /(\d{2})\/(\d{2})\/(\d{4})/.exec(s); if (m) return new Date(+m[3], +m[2] - 1, +m[1]).getTime(); return 0; };
  const todayEnd = (() => { const d = new Date(); d.setHours(23, 59, 59, 999); return d.getTime(); })();
  const relances = [
    ...investors.map(i => ({ id: i.id, name: i.name, when: i.relanceDate, next: i.nextAction, base: "investors" })),
    ...enseignes.map(e => ({ id: e.id, name: e.name, when: e.relanceDate, next: e.nextAction, base: "enseignes" })),
  ].filter(f => { const t = parseAnyDate(f.when); return t && t <= todayEnd; }).sort((a, b) => parseAnyDate(a.when) - parseAnyDate(b.when));

  // ── #8 Doublons (par nom normalisé) ──
  const dupGroups = (list) => { const g = {}; list.forEach(x => { const n = norm(x.name); if (n) (g[n] = g[n] || []).push(x); }); return Object.values(g).filter(a => a.length > 1); };
  const dupInv = dupGroups(investors), dupEns = dupGroups(enseignes);
  const dupCount = dupInv.length + dupEns.length;

  // ── #9 Pipeline (dossiers investisseurs + locaux enseignes) ──
  const pipeItems = [...investors.flatMap(i => (i.dossiers || [])), ...enseignes.flatMap(e => (e.locaux || []))];
  const pipeBy = {}; pipeItems.forEach(d => { const s = d.status || "Matché"; pipeBy[s] = (pipeBy[s] || 0) + 1; });
  const pipeTotal = pipeItems.length;
  const PIPE_STAGES = [["Matché", C.cyan], ["Envoyé", C.indigo], ["Intéressé", C.emerald], ["En négociation", C.violet], ["Signé", C.emerald]];
  const pipeMax = Math.max(1, ...PIPE_STAGES.map(([s]) => pipeBy[s] || 0));
  const sent = pipeBy["Envoyé"] || 0, interested = pipeBy["Intéressé"] || 0, signed = pipeBy["Signé"] || 0;

  // ── #7 Fraîcheur : sélectionner les fiches les plus anciennement enrichies ──
  const lastEnrichTime = (f) => (f.enrichments || []).reduce((a, e) => Math.max(a, parseFr(e.date)), 0);
  const selectOldest = (n = 5) => { const ids = [...baseList].sort((a, b) => lastEnrichTime(a) - lastEnrichTime(b)).slice(0, n).map(f => f.id); setEnrichSel(ids); setErr(""); };

  // ── #10 Sauvegarde / export JSON complet ──
  const exportBackup = () => {
    const payload = { app: "DoricMatch AI", exportedAt: new Date().toISOString(), investors, enseignes };
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a"); a.href = url; a.download = `doricmatch-sauvegarde-${new Date().toISOString().slice(0, 10)}.json`;
    document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
  };

  const actions = [
    ["⬆", "Analyser un dossier", "Brochure ou état locatif : l'IA extrait l'actif et identifie les investisseurs pertinents.", "upload", C.indigo],
    ["▭", "Analyser un local", "Annonce d'un local à louer (commerce ou bureaux) : l'IA matche les enseignes preneuses.", "uploadLocal", C.emerald],
    ["◴", "Base investisseurs", "Consultez, triez et complétez vos mandats d'acquisition. Export CSV / PDF.", "investors", C.violet],
    ["▦", "Base enseignes", "Vos enseignes en recherche de locaux. Cahier des charges, zones, enrichissement IA.", "enseignes", C.cyan],
  ];

  const tabBtn = (id, label) => (
    <button onClick={() => setBase(id)} style={{ padding: "7px 16px", borderRadius: 9, border: "none", cursor: "pointer", fontSize: 13, fontWeight: 600,
      background: base === id ? `linear-gradient(100deg,${C.indigo},${C.violet})` : "transparent", color: base === id ? "#fff" : C.dim }}>{label}</button>
  );

  return (
    <>
      <H sub="Vue d'ensemble de vos deux bases — investisseurs et enseignes — de votre pipeline et de vos outils.">Tableau de bord</H>

      <div className="grid-4" style={{ marginBottom: 18 }}>
        {kpis.map(([l, v, c, dest], i) => (
          <div key={l} className="card rise" onClick={() => dest && go(dest)} style={{ padding: 22, animationDelay: `${i * 60}ms`, cursor: dest ? "pointer" : "default" }}>
            <div className="serif" style={{ fontSize: 40, fontWeight: 700, color: c, lineHeight: 1 }}>{v}</div>
            <div style={{ fontSize: 12.5, color: C.dim, marginTop: 8 }}>{l}</div>
          </div>
        ))}
      </div>

      {/* #5 À relancer aujourd'hui + #9 Pipeline commercial */}
      <div className="grid-2" style={{ gap: 16, marginBottom: 18, alignItems: "stretch" }}>
        <div className="card rise" style={{ padding: 24 }}>
          <div className="serif" style={{ fontSize: 17, fontWeight: 700, marginBottom: 4 }}>À relancer aujourd'hui</div>
          <div style={{ fontSize: 12.5, color: C.dim, marginBottom: 14 }}>Fiches dont la date de relance est atteinte ou dépassée.</div>
          {relances.length === 0 ? <div style={{ fontSize: 13, color: C.faint }}>Aucune relance en attente. 🎉</div>
            : <div style={{ display: "grid", gap: 8, maxHeight: 230, overflowY: "auto" }}>
              {relances.slice(0, 40).map(r => (
                <div key={r.base + r.id} onClick={() => (r.base === "investors" ? onOpen : onOpenEns)(r.id)} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, padding: "8px 10px", borderRadius: 9, background: C.panel2, cursor: "pointer" }}>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: C.text, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.name}</div>
                    {r.next && <div style={{ fontSize: 11.5, color: C.dim, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.next}</div>}
                  </div>
                  <span style={{ fontSize: 11, fontWeight: 700, color: C.amber, whiteSpace: "nowrap" }}>{new Date(parseAnyDate(r.when)).toLocaleDateString("fr-FR")}</span>
                </div>
              ))}
            </div>}
          {relances.length > 0 && <div style={{ marginTop: 10, fontSize: 11.5, color: C.faint }}>{relances.length} relance(s) — cliquez pour ouvrir la fiche.</div>}
        </div>

        <div className="card rise" style={{ padding: 24 }}>
          <div className="serif" style={{ fontSize: 17, fontWeight: 700, marginBottom: 4 }}>Pipeline commercial</div>
          <div style={{ fontSize: 12.5, color: C.dim, marginBottom: 16 }}>{pipeTotal} dossier(s) / local(aux) rattaché(s)</div>
          {pipeTotal === 0 ? <div style={{ fontSize: 13, color: C.faint }}>Aucun dossier rattaché pour l'instant. Envoyez un dossier depuis les résultats de matching.</div>
            : <>
              {PIPE_STAGES.map(([s, c]) => { const v = pipeBy[s] || 0; return (
                <div key={s} style={{ marginBottom: 11 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, marginBottom: 5 }}><span>{s}</span><span style={{ color: C.dim }}>{v}</span></div>
                  <div style={{ height: 7, background: C.panel2, borderRadius: 6, overflow: "hidden" }}><div style={{ width: `${v / pipeMax * 100}%`, height: "100%", background: c, borderRadius: 6, transition: "width .5s" }} /></div>
                </div>
              ); })}
              <div style={{ display: "flex", gap: 22, marginTop: 14, fontSize: 12.5, color: C.dim, flexWrap: "wrap" }}>
                <span>Intéressé / envoyé : <b style={{ color: C.text }}>{sent ? Math.round(interested / sent * 100) : 0} %</b></span>
                <span>Signé / envoyé : <b style={{ color: C.text }}>{sent ? Math.round(signed / sent * 100) : 0} %</b></span>
              </div>
            </>}
        </div>
      </div>

      {/* #8 Doublons + #10 Sauvegarde */}
      <div className="card rise" style={{ padding: "14px 20px", marginBottom: 18, display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
        <span style={{ fontSize: 13, color: C.dim }}>
          {dupCount > 0
            ? <><b style={{ color: C.amber }}>⚠ {dupCount} doublon(s) potentiel(s)</b> par nom : {[...dupInv, ...dupEns].slice(0, 4).map(g => g[0].name).join(", ")}{dupCount > 4 ? "…" : ""}</>
            : <><b style={{ color: C.emerald }}>✓ Aucun doublon</b> détecté dans vos bases.</>}
        </span>
        <button className="btn btn-g" style={{ marginLeft: "auto" }} onClick={exportBackup}>⬇ Sauvegarder la base (JSON)</button>
      </div>

      {/* Répartition des deux bases */}
      <div className="grid-2" style={{ gap: 16, marginBottom: 18, alignItems: "stretch" }}>
        <div className="card rise" style={{ padding: 24, animationDelay: "240ms" }}>
          <div className="serif" style={{ fontSize: 17, fontWeight: 700, marginBottom: 4 }}>Répartition investisseurs</div>
          <div style={{ fontSize: 12.5, color: C.dim, marginBottom: 16 }}>Par typologie d'investisseur</div>
          {invMix.map(([l, v, c]) => (
            <div key={l} style={{ marginBottom: 12 }}>
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, marginBottom: 5 }}><span>{l}</span><span style={{ color: C.dim }}>{v}</span></div>
              <div style={{ height: 7, background: C.panel2, borderRadius: 6, overflow: "hidden" }}><div style={{ width: `${v / maxInvMix * 100}%`, height: "100%", background: c, borderRadius: 6, transition: "width .5s" }} /></div>
            </div>
          ))}
        </div>
        <div className="card rise" style={{ padding: 24, animationDelay: "280ms" }}>
          <div className="serif" style={{ fontSize: 17, fontWeight: 700, marginBottom: 4 }}>Répartition enseignes</div>
          <div style={{ fontSize: 12.5, color: C.dim, marginBottom: 16 }}>Par secteur d'activité</div>
          {ensMix.length === 0 ? <div style={{ fontSize: 13, color: C.faint, padding: "16px 0" }}>Aucune enseigne pour le moment. Créez-en une depuis la base enseignes.</div>
            : ensMix.map(([l, v, c]) => (
              <div key={l} style={{ marginBottom: 12 }}>
                <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, marginBottom: 5 }}><span>{l}</span><span style={{ color: C.dim }}>{v}</span></div>
                <div style={{ height: 7, background: C.panel2, borderRadius: 6, overflow: "hidden" }}><div style={{ width: `${v / maxEnsMix * 100}%`, height: "100%", background: c, borderRadius: 6, transition: "width .5s" }} /></div>
              </div>
            ))}
        </div>
      </div>

      {/* Deux CTA : placer un dossier · louer un local */}
      <div className="grid-2" style={{ gap: 16, marginBottom: 18 }}>
        <div className="card rise" style={{ padding: 24, animationDelay: "320ms", display: "flex", flexDirection: "column", justifyContent: "center", background: `linear-gradient(125deg,${C.sky},#fff)` }}>
          <div className="serif" style={{ fontSize: 21, fontWeight: 700 }}>Un dossier à placer ?</div>
          <div style={{ color: C.dim, fontSize: 13.5, marginTop: 8, lineHeight: 1.5 }}>L'IA extrait l'actif d'une brochure, puis classe vos investisseurs par pertinence avec un score de matching.</div>
          <div style={{ marginTop: 16 }}><button className="btn btn-p" onClick={() => go("upload")}>Analyser un dossier →</button></div>
        </div>
        <div className="card rise" style={{ padding: 24, animationDelay: "340ms", display: "flex", flexDirection: "column", justifyContent: "center", background: `linear-gradient(125deg,rgba(16,168,126,.12),#fff)` }}>
          <div className="serif" style={{ fontSize: 21, fontWeight: 700 }}>Un local à louer ?</div>
          <div style={{ color: C.dim, fontSize: 13.5, marginTop: 8, lineHeight: 1.5 }}>L'IA extrait le local (commerce ou bureaux), puis classe vos enseignes preneuses selon leur cahier des charges.</div>
          <div style={{ marginTop: 16 }}><button className="btn btn-p" onClick={() => go("uploadLocal")} style={{ background: `linear-gradient(100deg,${C.emerald},${C.cyan})` }}>Analyser un local →</button></div>
        </div>
      </div>

      {/* Historique des enrichissements (deux bases) */}
      <div className="card rise" style={{ padding: 24, marginBottom: 18, animationDelay: "360ms" }}>
        <div className="serif" style={{ fontSize: 17, fontWeight: 700, marginBottom: 4 }}>Historique des enrichissements IA</div>
        <div style={{ fontSize: 12.5, color: C.dim, marginBottom: 14 }}>Ce qui a été ajouté aux fiches investisseurs et enseignes, du plus récent au plus ancien.</div>
        {history.length === 0 ? (
          <div style={{ fontSize: 13, color: C.faint, padding: "20px 0", textAlign: "center" }}>Aucun enrichissement pour le moment.<br />Lancez l'enrichissement IA ci-dessous.</div>
        ) : (
          <div style={{ overflowY: "auto", maxHeight: 320, paddingRight: 4 }}>
            {history.map((h, i) => (
              <div key={i} style={{ padding: "10px 0", borderTop: i ? `1px solid ${C.line}` : "none" }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 10 }}>
                  <span style={{ fontSize: 13, fontWeight: 600, cursor: "pointer", color: C.indigo }} onClick={() => (h.base === "investors" ? onOpen : onOpenEns)(h.refId)}>
                    {h.refName} <span style={{ fontSize: 10, fontWeight: 600, color: h.base === "investors" ? C.violet : C.emerald, border: `1px solid ${h.base === "investors" ? C.violet : C.emerald}44`, padding: "1px 6px", borderRadius: 6, marginLeft: 4 }}>{h.base === "investors" ? "Investisseur" : "Enseigne"}</span>
                  </span>
                  <span style={{ fontSize: 11, color: C.faint, whiteSpace: "nowrap" }}>{h.date || "—"}</span>
                </div>
                <div style={{ fontSize: 12.5, color: C.dim, marginTop: 3 }}>{h.label && <b style={{ color: C.violet }}>{h.label} · </b>}{h.value}</div>
                {h.source && <div style={{ fontSize: 10.5, color: C.faint, marginTop: 2 }}>{h.source}{h.confidence ? ` · ${h.confidence} %` : ""}</div>}
              </div>
            ))}
          </div>
        )}
        {history.length > 0 && <div style={{ fontSize: 11, color: C.faint, marginTop: 12 }}>{history.length} entrée(s) au total</div>}
      </div>

      {/* Accès rapides */}
      <div style={{ fontSize: 12, textTransform: "uppercase", letterSpacing: "1.4px", color: C.faint, margin: "4px 0 12px" }}>Accès rapides</div>
      <div className="grid-4" style={{ marginBottom: 24 }}>
        {actions.map(([ic, title, desc, dest, c], i) => (
          <div key={title} className="card rise" onClick={() => go(dest)} style={{ padding: 20, cursor: "pointer", animationDelay: `${380 + i * 50}ms`, transition: ".15s" }}
            onMouseEnter={e => e.currentTarget.style.borderColor = c} onMouseLeave={e => e.currentTarget.style.borderColor = C.line}>
            <div style={{ width: 38, height: 38, borderRadius: 11, background: c + "1a", color: c, display: "grid", placeItems: "center", fontSize: 18, marginBottom: 12 }}>{ic}</div>
            <div style={{ fontSize: 14.5, fontWeight: 600 }}>{title}</div>
            <div style={{ fontSize: 12, color: C.dim, marginTop: 5, lineHeight: 1.45 }}>{desc}</div>
          </div>
        ))}
      </div>

      {/* Enrichissement IA — basculable investisseurs / enseignes */}
      <div className="card rise" style={{ padding: 24, animationDelay: "560ms" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 12 }}>
          <div>
            <div className="serif" style={{ fontSize: 19, fontWeight: 700 }}>Enrichissement IA</div>
            <div style={{ fontSize: 13, color: C.dim, marginTop: 4 }}>L'IA recherche les actualités {base === "investors" ? "de vos investisseurs (acquisitions, stratégie, critères d'achat)" : "de vos enseignes (plan de développement, surfaces, zones d'implantation)"} et propose des critères à ajouter aux fiches.</div>
            <div style={{ fontSize: 12, color: C.faint, marginTop: 6 }}>{enriched} fiche(s) déjà enrichie(s) au total</div>
          </div>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <button className="btn btn-g" onClick={runFillContacts} disabled={fillRunning || running || enrichSel.length === 0} style={{ opacity: (fillRunning || running || enrichSel.length === 0) ? .55 : 1 }} title="Recherche web des coordonnées manquantes (site, téléphone, contact) sur les fiches sélectionnées">
              {fillRunning ? `Complétion… ${fillDone}/${fillTotal}` : "☎ Compléter coordonnées"}</button>
            <button className="btn btn-p" onClick={runEnrichment} disabled={running || fillRunning || enrichSel.length === 0} style={{ opacity: (running || fillRunning || enrichSel.length === 0) ? .55 : 1 }}>
              {running ? "Recherche en cours…" : `⟐ Enrichir ${enrichSel.length || ""} fiche${enrichSel.length > 1 ? "s" : ""}`.trim()}</button>
          </div>
        </div>
        {fillProgress && <div style={{ fontSize: 12.5, color: C.indigo, marginTop: 10 }}>{fillProgress}</div>}
        {fillMsg && <div style={{ fontSize: 12.5, color: C.emerald, marginTop: 10 }}>✓ {fillMsg}</div>}

        {/* Sélecteur de base */}
        <div style={{ display: "inline-flex", gap: 4, marginTop: 14, padding: 4, borderRadius: 11, background: C.panel2, border: `1px solid ${C.line}` }}>
          {tabBtn("investors", `Investisseurs (${investors.length})`)}
          {tabBtn("enseignes", `Enseignes (${enseignes.length})`)}
        </div>

        <div style={{ marginTop: 14, padding: 14, borderRadius: 12, background: C.panel2, border: `1px solid ${C.line}` }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10, flexWrap: "wrap", gap: 8 }}>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
              <label style={{ display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 600 }}>
                <input type="checkbox" checked={allSelected} onChange={toggleAll} style={{ cursor: "pointer" }} /> Tout sélectionner
              </label>
              {baseList.length > 0 && <button className="btn btn-g" style={{ padding: "5px 11px", fontSize: 12 }} onClick={() => selectOldest(5)} title="Sélectionne les 5 fiches les plus anciennement enrichies, à rafraîchir">↻ Rafraîchir les plus anciennes</button>}
            </div>
            <span style={{ fontSize: 12, color: C.faint }}>{enrichSel.length} / {baseList.length} sélectionné(s) · 1 recherche IA par fiche</span>
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8, maxHeight: 180, overflowY: "auto" }}>
            {baseList.length === 0 ? <span style={{ fontSize: 13, color: C.faint }}>{base === "investors" ? "Aucun investisseur dans la base." : "Aucune enseigne dans la base."}</span>
              : baseList.map(i => {
                const on = enrichSel.includes(i.id);
                const never = !(i.enrichments && i.enrichments.length);
                return (
                  <span key={i.id} onClick={() => toggleEnrich(i.id)} className="chip" title={never ? "Jamais enrichi par l'IA" : "Déjà enrichi"} style={{ cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6,
                    background: on ? `linear-gradient(100deg,${C.indigo},${C.violet})` : undefined, color: on ? "#fff" : undefined, border: on ? "none" : undefined }}>
                    {never && <span style={{ width: 7, height: 7, borderRadius: "50%", background: on ? "#fff" : C.amber, flex: "none" }} />}
                    <span style={{ fontSize: 11 }}>{on ? "✓" : "+"}</span>{i.name}
                  </span>
                );
              })}
          </div>
          <div style={{ fontSize: 11, color: C.faint, marginTop: 8, display: "flex", alignItems: "center", gap: 6 }}>
            <span style={{ width: 7, height: 7, borderRadius: "50%", background: C.amber, display: "inline-block" }} /> = jamais enrichi par l'IA
          </div>
        </div>

        {running && (
          <div style={{ marginTop: 16 }}>
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, color: C.dim, marginBottom: 6 }}><span>{progress || "Recherche en cours…"}</span><span>{enrichDone}/{enrichTotal} · {elapsed}s</span></div>
            <div style={{ height: 8, background: C.panel2, borderRadius: 6, overflow: "hidden" }}><div style={{ height: "100%", width: (enrichTotal ? Math.round(enrichDone / enrichTotal * 100) : 0) + "%", background: `linear-gradient(90deg,${C.indigo},${C.violet})`, borderRadius: 6, transition: "width .4s" }} /></div>
          </div>
        )}
        {err && <div style={{ marginTop: 14, fontSize: 13, color: C.amber }}>{err}</div>}
      </div>

      {results.length > 0 && (
        <div className="rise" style={{ marginTop: 22 }}>
          <div style={{ fontSize: 12, textTransform: "uppercase", letterSpacing: "1.4px", color: C.faint, marginBottom: 14 }}>Informations trouvées — à valider et ajouter aux fiches</div>
          {results.map((r, ri) => (
            <div key={ri} className="card" style={{ padding: 20, marginBottom: 12 }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
                <span className="serif" style={{ fontSize: 17, fontWeight: 600, cursor: "pointer" }} onClick={() => openFiche(r)}>{r.name}
                  <span style={{ fontSize: 10, fontWeight: 600, color: r.base === "investors" ? C.violet : C.emerald, border: `1px solid ${r.base === "investors" ? C.violet : C.emerald}44`, padding: "1px 6px", borderRadius: 6, marginLeft: 8 }}>{r.base === "investors" ? "Investisseur" : "Enseigne"}</span>
                </span>
                <span style={{ fontSize: 12, color: C.indigo, cursor: "pointer" }} onClick={() => openFiche(r)}>Voir la fiche →</span>
              </div>
              {r.found.map((f, fi) => (
                <div key={fi} style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, padding: "10px 0", borderTop: fi ? `1px solid ${C.line}` : "none" }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13.5 }}><b style={{ color: C.violet }}>{(r.base === "investors" ? FIELD_LABELS : ENS_FIELD_LABELS)[f.field] || "Info"}</b> · {f.value}</div>
                    <div style={{ fontSize: 11.5, color: C.faint, marginTop: 3, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                      <span className="chip" style={{ padding: "1px 7px" }}>{f.source}</span><span>fiabilité {f.confidence} %</span>{f.url && <a href={f.url} target="_blank" rel="noreferrer">↗ source</a>}
                    </div>
                  </div>
                  {f.applied ? <span style={{ fontSize: 12, color: C.emerald, fontWeight: 600, whiteSpace: "nowrap" }}>✓ Ajouté</span>
                    : <button className="btn btn-g" style={{ padding: "7px 13px", whiteSpace: "nowrap" }} onClick={() => applyOne(ri, fi)}>+ Ajouter à la fiche</button>}
                </div>
              ))}
              {r.criteria && r.criteria.length > 0 && (
                <div style={{ marginTop: 12, paddingTop: 12, borderTop: `1px solid ${C.line}` }}>
                  <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: ".8px", color: C.indigo, marginBottom: 8 }}>Critères à affiner (impactent le matching)</div>
                  {r.criteria.map((c, ci) => (
                    <div key={ci} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "8px 0", borderTop: ci ? `1px solid ${C.line}` : "none" }}>
                      <div style={{ fontSize: 13 }}><b style={{ color: C.indigo }}>{(r.base === "investors" ? CRIT_LABELS : ENS_CRIT_LABELS)[c.field] || c.label || "Critère"}</b> · {c.value}<span style={{ fontSize: 11, color: C.faint, marginLeft: 6 }}>({c.confidence} %)</span></div>
                      {c.applied ? <span style={{ fontSize: 12, color: C.emerald, fontWeight: 600, whiteSpace: "nowrap" }}>✓ Critère ajouté</span>
                        : <button className="btn btn-p" style={{ padding: "7px 13px", whiteSpace: "nowrap" }} onClick={() => applyCrit(ri, ci)}>+ Affiner le matching</button>}
                    </div>
                  ))}
                </div>
              )}
            </div>
          ))}
        </div>
      )}

      {/* ── Suivi LinkedIn des contacts : changements de poste / de société ── */}
      <div className="card rise" style={{ padding: 24, marginTop: 22 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 12 }}>
          <div>
            <div className="serif" style={{ fontSize: 19, fontWeight: 700 }}>Suivi LinkedIn des contacts</div>
            <div style={{ fontSize: 13, color: C.dim, marginTop: 4 }}>L'IA vérifie sur LinkedIn si chaque contact est toujours à son poste, ou s'il a changé de poste ou de société. Vous validez chaque mise à jour d'un clic.</div>
            <div style={{ fontSize: 12, color: C.faint, marginTop: 6 }}>Base : {base === "investors" ? "investisseurs" : "enseignes"} (bascule via le sélecteur ci-dessus)</div>
          </div>
          <button className="btn btn-p" onClick={runContactCheck} disabled={ctcRunning || ctcSel.length === 0} style={{ opacity: (ctcRunning || ctcSel.length === 0) ? .55 : 1, background: `linear-gradient(100deg,#0a66c2,${C.violet})` }}>
            {ctcRunning ? "Vérification en cours…" : `in Vérifier ${ctcCount || ""} contact${ctcCount > 1 ? "s" : ""}`.trim()}</button>
        </div>

        <div style={{ marginTop: 14, padding: 14, borderRadius: 12, background: C.panel2, border: `1px solid ${C.line}` }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10, flexWrap: "wrap", gap: 8 }}>
            <label style={{ display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 13, fontWeight: 600 }}>
              <input type="checkbox" checked={ctcAllSel} onChange={ctcToggleAll} style={{ cursor: "pointer" }} /> Tout sélectionner
            </label>
            <span style={{ fontSize: 12, color: C.faint }}>{ctcSel.length} / {baseList.length} fiche(s) · {ctcCount} contact(s) à vérifier</span>
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8, maxHeight: 180, overflowY: "auto" }}>
            {baseList.length === 0 ? <span style={{ fontSize: 13, color: C.faint }}>{base === "investors" ? "Aucun investisseur dans la base." : "Aucune enseigne dans la base."}</span>
              : baseList.map(i => {
                const on = ctcSel.includes(i.id);
                const nb = (i.contacts || []).filter(c => (c.name || "").trim()).length;
                return (
                  <span key={i.id} onClick={() => ctcToggle(i.id)} className="chip" title={`${nb} contact(s) nommé(s)`} style={{ cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 6, opacity: nb ? 1 : .5,
                    background: on ? `linear-gradient(100deg,#0a66c2,${C.violet})` : undefined, color: on ? "#fff" : undefined, border: on ? "none" : undefined }}>
                    <span style={{ fontSize: 11 }}>{on ? "✓" : "+"}</span>{i.name}{nb ? <span style={{ fontSize: 10, opacity: .8 }}>· {nb}</span> : null}
                  </span>
                );
              })}
          </div>
        </div>

        {ctcRunning && (
          <div style={{ marginTop: 16 }}>
            <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12.5, color: C.dim, marginBottom: 6 }}><span>{ctcProgress || "Vérification en cours…"}</span><span>{ctcDone}/{ctcTotal}</span></div>
            <div style={{ height: 8, background: C.panel2, borderRadius: 6, overflow: "hidden" }}><div style={{ height: "100%", width: (ctcTotal ? Math.round(ctcDone / ctcTotal * 100) : 0) + "%", background: `linear-gradient(90deg,#0a66c2,${C.violet})`, borderRadius: 6, transition: "width .4s" }} /></div>
          </div>
        )}
        {ctcErr && <div style={{ marginTop: 14, fontSize: 13, color: C.amber }}>{ctcErr}</div>}
      </div>

      {ctcResults.length > 0 && (
        <div className="rise" style={{ marginTop: 18 }}>
          <div style={{ fontSize: 12, textTransform: "uppercase", letterSpacing: "1.4px", color: C.faint, marginBottom: 14 }}>
            Résultats du suivi contacts — {ctcChanges} changement(s) détecté(s) sur {ctcResults.length} contact(s)
          </div>
          {ctcResults.map((r) => {
            const st = CTC_STATUS[r.status] || CTC_STATUS.inchange;
            return (
              <div key={r.key} className="card" style={{ padding: 18, marginBottom: 12, borderLeft: `3px solid ${st.color}` }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 10, flexWrap: "wrap" }}>
                  <div style={{ fontSize: 15, fontWeight: 600 }}>{r.contactName}
                    <span style={{ fontSize: 12, color: C.dim, fontWeight: 400 }}> · {r.contact.role || "poste n.c."} chez <span style={{ cursor: "pointer", color: C.indigo }} onClick={() => (r.base === "investors" ? onOpen : onOpenEns)(r.ficheId)}>{r.ficheName}</span></span>
                  </div>
                  <span style={{ fontSize: 11, fontWeight: 700, color: "#fff", background: st.color, padding: "3px 10px", borderRadius: 8, whiteSpace: "nowrap" }}>{st.label}</span>
                </div>
                {r.message && <div style={{ fontSize: 13, color: C.text, marginTop: 8, lineHeight: 1.5 }}>{r.message}</div>}
                {(r.newRole || r.newCompany) && (
                  <div style={{ fontSize: 12.5, color: C.dim, marginTop: 6 }}>
                    {r.newRole && <span>Nouveau poste : <b style={{ color: C.violet }}>{r.newRole}</b>. </span>}
                    {r.newCompany && <span>Nouvelle société : <b style={{ color: C.violet }}>{r.newCompany}</b>{r.newCompanyType ? <span> (type : {r.newCompanyType})</span> : null}{r.newCompanyCity ? <span> · {r.newCompanyCity}</span> : null}. </span>}
                    {r.newLinkedin && <a href={r.newLinkedin} target="_blank" rel="noreferrer" style={{ fontSize: 11.5 }}>↗ profil</a>}
                  </div>
                )}

                {/* Actions proposées */}
                <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 8 }}>
                  {/* Société déjà dans la base → déplacer le contact */}
                  {r.newCompany && r.matchCompany && (
                    r.done.move
                      ? <span style={{ fontSize: 12.5, color: C.emerald, fontWeight: 600 }}>✓ Contact déplacé vers {r.matchCompany.name}</span>
                      : <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, flexWrap: "wrap", background: C.panel2, borderRadius: 10, padding: "8px 12px" }}>
                          <span style={{ fontSize: 12.5 }}>« {r.matchCompany.name} » est déjà dans la base — déplacer le contact.</span>
                          <button className="btn btn-p" style={{ padding: "6px 12px", whiteSpace: "nowrap" }} onClick={() => doMove(r)}>→ Déplacer vers {r.matchCompany.name}</button>
                        </div>
                  )}
                  {/* Société inconnue → indiquer le type + créer + déplacer */}
                  {r.newCompany && !r.matchCompany && (
                    r.done.create
                      ? <span style={{ fontSize: 12.5, color: C.emerald, fontWeight: 600 }}>✓ Société « {r.newCompany} » créée, contact déplacé</span>
                      : <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, flexWrap: "wrap", background: C.panel2, borderRadius: 10, padding: "8px 12px" }}>
                          <span style={{ fontSize: 12.5 }}>« {r.newCompany} » n'est pas dans la base{r.newCompanyType ? <span> — type : <b>{r.newCompanyType}</b></span> : null}.</span>
                          <button className="btn btn-g" style={{ padding: "6px 12px", whiteSpace: "nowrap" }} onClick={() => doCreateMove(r)}>+ Créer la {base === "investors" ? "société" : "enseigne"} & déplacer</button>
                        </div>
                  )}
                  {/* Remplaçant au même poste → ajouter comme contact */}
                  {r.replacement && (
                    r.done.add
                      ? <span style={{ fontSize: 12.5, color: C.emerald, fontWeight: 600 }}>✓ {r.replacement.name} ajouté à {r.ficheName}</span>
                      : <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, flexWrap: "wrap", background: C.panel2, borderRadius: 10, padding: "8px 12px" }}>
                          <span style={{ fontSize: 12.5 }}>Remplaçant identifié : <b>{r.replacement.name}</b>{r.replacement.role ? " — " + r.replacement.role : ""} — l'ajouter chez {r.ficheName}.</span>
                          <button className="btn btn-g" style={{ padding: "6px 12px", whiteSpace: "nowrap" }} onClick={() => doAddReplacement(r)}>+ Ajouter le remplaçant</button>
                        </div>
                  )}
                  {r.status === "inchange" && <span style={{ fontSize: 12.5, color: C.emerald }}>✓ Toujours en poste — aucune action nécessaire.</span>}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </>
  );
}

function MatchHistory({ history, onOpenInv, onOpenEns }) {
  const [openRun, setOpenRun] = useState(null);
  const [filter, setFilter] = useState("all"); // all | investors | enseignes
  if (!history || !history.length) {
    return <div style={{ fontSize: 13.5, color: C.faint, padding: "40px 0", textAlign: "center" }}>Aucun matching enregistré pour le moment.<br />Analysez un dossier ou un local : chaque matching est archivé ici automatiquement.</div>;
  }
  const fmtDate = (iso) => { try { return new Date(iso).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }); } catch (_) { return ""; } };
  const shownHist = history.filter(r => filter === "all" || r.kind === filter);
  const fBtn = (id, label) => (
    <button onClick={() => setFilter(id)} style={{ padding: "5px 12px", borderRadius: 8, border: `1px solid ${filter === id ? C.indigo : C.line}`, cursor: "pointer", fontSize: 12, fontWeight: 600, background: filter === id ? C.indigo + "14" : "transparent", color: filter === id ? C.indigo : C.dim }}>{label}</button>
  );
  return (
    <div>
      <div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
        {fBtn("all", `Tous (${history.length})`)}
        {fBtn("investors", "Dossiers → investisseurs")}
        {fBtn("enseignes", "Locaux → enseignes")}
      </div>
      {shownHist.length === 0 && <div style={{ fontSize: 13, color: C.faint, padding: "20px 0" }}>Aucun matching pour ce filtre.</div>}
      {shownHist.map(run => {
        const isInv = run.kind === "investors";
        const onOpen = isInv ? onOpenInv : onOpenEns;
        const s = run.subject || {};
        const open = openRun === run.runId;
        const ms = run.matches || [];
        const shown = open ? ms : ms.slice(0, 5);
        const ai = run.ai || {};
        return (
          <div key={run.runId} className="card rise" style={{ padding: 20, marginBottom: 14 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12, flexWrap: "wrap" }}>
              <div>
                <div className="serif" style={{ fontSize: 18, fontWeight: 700 }}>{s.title || (isInv ? "Dossier" : "Local")}
                  <span style={{ fontSize: 10, fontWeight: 600, color: isInv ? C.violet : C.emerald, border: `1px solid ${(isInv ? C.violet : C.emerald)}44`, padding: "1px 7px", borderRadius: 6, marginLeft: 8 }}>{isInv ? "Dossier → investisseurs" : "Local → enseignes"}</span>
                </div>
                <div style={{ fontSize: 12.5, color: C.dim, marginTop: 4 }}>{[s.localisation, s.actif || s.type, s.rendement || s.loyer].filter(Boolean).join(" · ") || "—"}</div>
              </div>
              <div style={{ textAlign: "right" }}>
                <div style={{ fontSize: 11.5, color: C.faint }}>{fmtDate(run.date)}</div>
                <div style={{ fontSize: 11.5, color: run.ai ? C.indigo : C.faint, marginTop: 3 }}>{run.ai ? "✦ Affiné par l'IA" : "Score algorithmique"}</div>
              </div>
            </div>
            <div style={{ marginTop: 14 }}>
              <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: ".8px", color: C.faint, marginBottom: 6 }}>{isInv ? "Investisseurs" : "Enseignes"} susceptibles de correspondre</div>
              {ms.length === 0 ? <div style={{ fontSize: 12.5, color: C.faint }}>Aucune fiche enregistrée.</div>
                : shown.map((m, i) => {
                  const t = tier(m.global);
                  const a = ai[m.id];
                  const retenu = a && a.verdict;
                  return (
                    <div key={m.id + "_" + i} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, padding: "8px 0", borderTop: i ? `1px solid ${C.line}` : "none" }}>
                      <div style={{ minWidth: 0 }}>
                        <span style={{ fontSize: 13.5, fontWeight: 600, cursor: "pointer", color: C.indigo }} onClick={() => onOpen(m.id)}>{m.name}</span>
                        {m.sub && <span style={{ fontSize: 11.5, color: C.faint }}> · {m.sub}</span>}
                        <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 4 }}>
                          {(m.matched || []).slice(0, 4).map(x => <span key={x} style={{ fontSize: 10.5, background: "rgba(16,168,126,.12)", color: C.emerald, padding: "2px 8px", borderRadius: 7 }}>✓ {x}</span>)}
                          {retenu && <span style={{ fontSize: 10.5, fontWeight: 700, color: "#fff", background: (VERDICT_C[(a.verdict || "").toLowerCase()] || C.dim), padding: "2px 8px", borderRadius: 7 }}>IA · {a.verdict}</span>}
                        </div>
                      </div>
                      <div style={{ display: "flex", alignItems: "center", gap: 12, whiteSpace: "nowrap" }}>
                        {a && a.aiScore != null && <span style={{ fontSize: 12, color: C.indigo }}>IA {a.aiScore}</span>}
                        <span style={{ fontSize: 16, fontWeight: 700, color: t.c }}>{m.global}</span>
                      </div>
                    </div>
                  );
                })}
            </div>
            {ms.length > 5 && <div style={{ fontSize: 12, color: C.indigo, cursor: "pointer", marginTop: 8 }} onClick={() => setOpenRun(open ? null : run.runId)}>{open ? "▲ Réduire" : `▼ Voir les ${ms.length} fiches`}</div>}
          </div>
        );
      })}
    </div>
  );
}
