/* global React */
// Dossier detail — full-bleed hero + cockpit panels + inline copilot

function DossierDetail({ d, onClose, onOpenSignature }) {
  const stage = STAGES.find(s => s.key === d.stage);
  const [tab, setTab] = useState('overview');
  const [oppEditOpen, setOppEditOpen] = useState(false);
  const [dossierEditOpen, setDossierEditOpen] = useState(false);

  return (
    <div style={{ padding: '16px 24px 80px', maxWidth: 1600, margin: '0 auto' }}>
      {/* Breadcrumb */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--ink-4)', marginBottom: 12 }}>
        <button onClick={onClose} style={{ color: 'var(--ink-3)' }}>← Dossiers</button>
        <span>/</span>
        <span style={{ color: 'var(--ink-2)' }}>{d.client}</span>
        <span>/</span>
        <span className="mono" style={{ color: 'var(--ink-2)' }}>{d.ref}</span>
      </div>

      {/* Hero header */}
      <div style={{
        border: '1px solid var(--line)', borderRadius: 10,
        background: 'var(--paper-2)', padding: '20px 24px',
        marginBottom: 16, position: 'relative', overflow: 'hidden',
      }}>
        {/* Subtle scanline accent */}
        <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 2, background: `linear-gradient(90deg, var(--signal) 0%, transparent 60%)` }} />

        <div style={{ display: 'grid', gridTemplateColumns: '64px 1fr auto', gap: 20, alignItems: 'flex-start' }}>
          <Sigil seed={d.ref} size={64} />
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
              <RefChip r={d.ref} />
              <span style={{ fontSize: 11, color: 'var(--ink-4)' }}>crée {d.created}</span>
              <span style={{ color: 'var(--ink-5)' }}>·</span>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                <Dot tone={stage.tone} size={6} pulse />
                <span style={{ fontSize: 12, fontWeight: 600 }}>{stage.label}</span>
                <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{stage.sub}</span>
              </span>
            </div>
            <h1 style={{ fontSize: 26, fontWeight: 600, letterSpacing: '-0.02em', margin: '0 0 4px' }}>{d.client}</h1>
            <div style={{ fontSize: 12, color: 'var(--ink-3)', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
              <span className="mono">{d.fost}</span>
              <span>{d.fostName}</span>
              <span style={{ color: 'var(--ink-5)' }}>·</span>
              <span>{d.city} · zone <span className="mono">{d.zone}</span></span>
              <span style={{ color: 'var(--ink-5)' }}>·</span>
              <span>Propriétaire : {d.owner}</span>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <Btn variant="outline" size="md" onClick={() => setDossierEditOpen(true)}>✏️ Éditer dossier</Btn>
            <Btn variant="outline" size="md" onClick={() => setOppEditOpen(true)}>⚙️ Éditer opp</Btn>
            <Btn variant="outline" size="md">Audit inversé</Btn>
            <Btn variant="outline" size="md" icon={<Icon.sign />} onClick={onOpenSignature}>Signatures</Btn>
            <Btn variant="signal" size="md" iconRight={<Icon.arrow />}>Faire progresser</Btn>
          </div>
        </div>
        {oppEditOpen && window.OpportunityDetailModal && (
          <window.OpportunityDetailModal
            dossier={d}
            onClose={() => setOppEditOpen(false)}
            onUpdated={() => {}}
            onDeleted={() => { setOppEditOpen(false); onClose(); }}
          />
        )}

        {/* Stage bar */}
        <div style={{ marginTop: 22, display: 'grid', gridTemplateColumns: `repeat(${STAGES.length}, 1fr)`, gap: 0 }}>
          {STAGES.map((s, i) => {
            const idx = STAGES.findIndex(x => x.key === d.stage);
            const done = i < idx;
            const current = i === idx;
            return (
              <div key={s.key} style={{
                padding: '10px 6px 8px',
                borderTop: `2px solid ${done ? 'var(--signal)' : current ? 'var(--ink)' : 'var(--line-2)'}`,
                position: 'relative',
                opacity: (done || current) ? 1 : 0.5,
              }}>
                <div style={{ fontSize: 10, color: 'var(--ink-4)' }} className="mono">{String(i+1).padStart(2,'0')}</div>
                <div style={{ fontSize: 11, fontWeight: current ? 700 : 500, color: current ? 'var(--ink)' : 'var(--ink-3)', lineHeight: 1.2 }}>{s.label}</div>
                {current && <div style={{ position: 'absolute', top: -5, left: 0, width: 8, height: 8, borderRadius: '50%', background: 'var(--ink)', animation: 'breathe 2.4s infinite' }} />}
              </div>
            );
          })}
        </div>
      </div>

      {/* Tabs */}
      <div style={{ display: 'flex', gap: 4, borderBottom: '1px solid var(--line)', marginBottom: 16 }}>
        {[
          { k: 'overview', l: 'Vue 360°' },
          { k: 'pieces', l: 'Pièces · OCR' },
          { k: 'calcul', l: 'Calcul cumac' },
          { k: 'signatures', l: 'Signatures' },
          { k: 'rdv', l: '📅 RDV' },
          { k: 'timeline', l: 'Timeline' },
          { k: 'audit', l: 'Audit inversé' },
          { k: 'portal', l: '🌐 Portail client' },
          { k: 'chatter', l: '📎 Pièces & notes' },
          { k: 'comments', l: '💬 Discussion' },
        ].map(t => (
          <button key={t.k} onClick={() => setTab(t.k)} style={{
            padding: '8px 12px', fontSize: 12, fontWeight: 500,
            color: tab === t.k ? 'var(--ink)' : 'var(--ink-4)',
            borderBottom: tab === t.k ? '2px solid var(--ink)' : '2px solid transparent',
            marginBottom: -1,
          }}>{t.l}</button>
        ))}
      </div>

      {tab === 'overview' && <DossierOverview d={d} onOpenSignature={onOpenSignature} />}
      {tab === 'pieces' && <DossierPieces d={d} />}
      {tab === 'calcul' && <DossierCalcul d={d} />}
      {tab === 'signatures' && <DossierSignatures d={d} onOpenSignature={onOpenSignature} />}
      {tab === 'rdv' && <DossierAppointments d={d} />}
      {tab === 'timeline' && <DossierTimeline d={d} />}
      {tab === 'audit' && <DossierAudit d={d} />}
      {tab === 'portal' && <DossierPortal d={d} />}
      {tab === 'chatter' && (
        window.ChatterPanel && d.id
          ? <window.ChatterPanel entityType="cee_dossier" entityId={d.id} />
          : <div style={{ padding: 20, fontSize: 12, color: 'var(--ink-4)' }}>{d.id ? 'Module chatter non chargé.' : 'Dossier non persisté — enregistre-le d\'abord.'}</div>
      )}
      {tab === 'comments' && (
        window.EntityCommentsPanel
          ? <window.EntityCommentsPanel targetKind="dossier" targetId={d.id || d.ref} />
          : <div style={{ padding: 20, fontSize: 12, color: 'var(--ink-4)' }}>Module Organisation non chargé.</div>
      )}

      {dossierEditOpen && <DossierEditModal d={d} onClose={() => setDossierEditOpen(false)} onDeleted={() => { setDossierEditOpen(false); onClose(); }} />}
    </div>
  );
}

// ─── Modal édition + suppression Dossier CEE (P2 — Règle n°5 CRUD) ──────────
function DossierEditModal({ d, onClose, onDeleted }) {
  const API = (window.AE_API && window.AE_API.BASE) || '';
  const [draftId, setDraftId] = useState(null); // ID backend du brouillon si trouvé
  const [loading, setLoading] = useState(true);
  const [draft, setDraft] = useState({
    clientName: d.client || '', clientAddress: d.city || '', city: d.city || '',
    fostCode: d.fost || '', assignedTo: d.owner || '', notes: d.notes || '',
  });
  const [saving, setSaving] = useState(false);
  const [msg, setMsg] = useState(null);

  // Chercher le brouillon backend correspondant (par clientName ou fostCode)
  useEffect(() => {
    fetch(`${API}/api/cee/drafts?limit=200`).then(r => r.json()).then(res => {
      const all = res?.drafts || [];
      const hit = all.find(x =>
        (x.clientName && x.clientName.toLowerCase() === (d.client || '').toLowerCase()) ||
        (x.fostCode && x.fostCode === d.fost)
      );
      if (hit) {
        setDraftId(hit.id);
        setDraft(prev => ({ ...prev, ...{ clientName: hit.clientName || prev.clientName, clientAddress: hit.clientAddress || prev.clientAddress, city: hit.city || prev.city, fostCode: hit.fostCode || prev.fostCode, assignedTo: hit.assignedTo || prev.assignedTo, notes: hit.notes || prev.notes } }));
      }
      setLoading(false);
    }).catch(() => setLoading(false));
  }, [d.ref]);

  const save = async () => {
    setSaving(true); setMsg(null);
    if (!draftId) { setMsg('⚠ Brouillon non trouvé en base — non synchronisé (données maquette)'); setSaving(false); return; }
    const r = await fetch(`${API}/api/cee/drafts/${draftId}`, {
      method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(draft),
    }).then(r => r.json()).catch(() => null);
    setSaving(false);
    if (r?.ok) { setMsg('✓ Dossier mis à jour'); setTimeout(() => onClose(), 1200); }
    else setMsg(`⚠ ${r?.error || 'Erreur de sauvegarde'}`);
  };

  const deleteDraft = async () => {
    if (!draftId) { setMsg('⚠ Brouillon non trouvé en base'); return; }
    if (!window.confirm(`Supprimer définitivement le dossier ${d.ref} · ${d.client} ?`)) return;
    const r = await fetch(`${API}/api/cee/drafts/${draftId}`, { method: 'DELETE' }).then(r => r.json()).catch(() => null);
    if (r?.ok) { onDeleted && onDeleted(); }
    else setMsg(`⚠ ${r?.error || 'Erreur suppression'}`);
  };

  const inpS = { padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box' };
  const lblS = { fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 };

  return (
    <div onClick={e => { if (e.target === e.currentTarget) onClose(); }}
      style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(14,16,16,0.6)', display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)', padding: 16 }}>
      <div style={{ width: 'min(540px, 100%)', background: 'var(--paper)', borderRadius: 12, boxShadow: 'var(--shadow-3)', border: '1px solid var(--line-2)' }}>
        <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>✏️ Éditer le dossier</div>
            <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>{d.ref} · {d.client}</div>
          </div>
          <span style={{ flex: 1 }} />
          {!loading && !draftId && <span style={{ fontSize: 10, padding: '2px 7px', background: 'var(--amber-tint)', color: 'var(--amber)', border: '1px solid var(--amber)', borderRadius: 3 }}>données maquette</span>}
          {!loading && draftId && <span style={{ fontSize: 10, padding: '2px 7px', background: 'var(--signal-tint)', color: 'var(--signal-deep)', border: '1px solid var(--signal-soft)', borderRadius: 3 }}>synchronisé · brouillon #{draftId}</span>}
          <button onClick={onClose} style={{ color: 'var(--ink-4)', fontSize: 16 }}>✕</button>
        </div>
        <div style={{ padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 10 }}>
          {loading && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Recherche du brouillon en base…</div>}
          {msg && <div style={{ padding: '6px 10px', fontSize: 11, background: msg.startsWith('✓') ? 'var(--signal-tint)' : 'var(--rouge-tint)', border: '1px solid ' + (msg.startsWith('✓') ? 'var(--signal-soft)' : 'var(--rouge)'), borderRadius: 5, color: msg.startsWith('✓') ? 'var(--signal-deep)' : 'var(--rouge)' }}>{msg}</div>}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            <label style={{ gridColumn: '1 / -1', display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={lblS}>Nom client</span>
              <input value={draft.clientName} onChange={e => setDraft(x => ({ ...x, clientName: e.target.value }))} style={inpS} />
            </label>
            <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={lblS}>Adresse chantier</span>
              <input value={draft.clientAddress} onChange={e => setDraft(x => ({ ...x, clientAddress: e.target.value }))} style={inpS} />
            </label>
            <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={lblS}>Ville</span>
              <input value={draft.city} onChange={e => setDraft(x => ({ ...x, city: e.target.value }))} style={inpS} />
            </label>
            <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={lblS}>Code FOST</span>
              <input value={draft.fostCode} onChange={e => setDraft(x => ({ ...x, fostCode: e.target.value }))} style={inpS} />
            </label>
            <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={lblS}>Responsable</span>
              <input value={draft.assignedTo} onChange={e => setDraft(x => ({ ...x, assignedTo: e.target.value }))} style={inpS} />
            </label>
            <label style={{ gridColumn: '1 / -1', display: 'flex', flexDirection: 'column', gap: 3 }}>
              <span style={lblS}>Notes internes</span>
              <textarea value={draft.notes} onChange={e => setDraft(x => ({ ...x, notes: e.target.value }))} rows={3} style={{ ...inpS, resize: 'vertical' }} />
            </label>
          </div>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'space-between', marginTop: 4 }}>
            <button onClick={deleteDraft} disabled={saving} style={{ padding: '7px 12px', fontSize: 11, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 5, fontWeight: 600 }}>
              🗑 Supprimer
            </button>
            <div style={{ display: 'flex', gap: 6 }}>
              <button onClick={onClose} disabled={saving} style={{ padding: '7px 12px', fontSize: 11, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 5 }}>Annuler</button>
              <button onClick={save} disabled={saving || loading} style={{ padding: '7px 14px', fontSize: 11, fontWeight: 600, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 5 }}>
                {saving ? '…' : '✓ Enregistrer'}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function DossierOverview({ d, onOpenSignature }) {
  const riskTone = d.risk >= 80 ? 'signal' : d.risk >= 60 ? 'amber' : 'rouge';
  const [liveCoherence, setLiveCoherence] = useState(null);
  const [sendState, setSendState] = useState({ loading: false, result: null });
  useEffect(() => {
    let cancelled = false;
    if (window.AE_API?.coherence) {
      window.AE_API.coherence(d).then((res) => { if (!cancelled) setLiveCoherence(res); });
    }
    return () => { cancelled = true; };
  }, [d.ref]);
  const liveScore = liveCoherence ? liveCoherence.score : d.coherence;
  const liveErrors = liveCoherence ? (liveCoherence.errors || []) : [];
  const onSendForApproval = async () => {
    if (!window.AE_API?.buildAndSend) return;
    setSendState({ loading: true, result: null });
    const res = await window.AE_API.buildAndSend(d);
    setSendState({ loading: false, result: res });
  };
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 380px', gap: 16 }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        {/* KPIs row */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0, border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', overflow: 'hidden' }}>
          <MiniKpi label="MWh cumac" value={fmtMWh(d.cumac)} accent="var(--signal)" />
          <MiniKpi label="Prime" value={fmtEur(d.amount)} accent="var(--signal)" sub={d.cumac > 0 ? `spot · ${(d.amount/d.cumac*1000).toFixed(2)} €/MWh` : 'pas de cumac encore'} divider />
          <MiniKpi label="Score PNCEE" value={`${d.risk}/100`} accent={riskTone === 'signal' ? 'var(--signal-deep)' : riskTone === 'amber' ? 'var(--amber)' : 'var(--rouge)'} sub="probabilité acceptation" divider />
          <MiniKpi label="Cohérence" value={`${liveScore} %`} accent="var(--ink)" sub={liveCoherence ? `${liveErrors.length} erreur${liveErrors.length > 1 ? 's' : ''} backend` : `${d.pieces.ok}/${d.pieces.total} pièces OK`} divider />
        </div>

        {/* Client card */}
        <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--hairline)', display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 13, fontWeight: 600 }}>Bénéficiaire</span>
            <Badge tone="signal" icon={<Icon.check />}>SIRET vérifié</Badge>
            <Badge tone="plasma">Sync Odoo · il y a 14 min</Badge>
            <span style={{ flex: 1 }} />
            <button style={{ fontSize: 11, color: 'var(--ink-3)' }}>Éditer</button>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0 }}>
            {[
              ['Raison sociale', d.client],
              ['SIRET', d.siret],
              ['Adresse chantier', `${d.city}, zone ${d.zone}`],
              ['Mandataire', 'Audits-Énergies SAS'],
            ].map(([k, v], i) => (
              <div key={k} style={{ padding: '12px 16px', borderRight: i < 3 ? '1px solid var(--hairline)' : 'none' }}>
                <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 500 }}>{k}</div>
                <div style={{ fontSize: 12, color: 'var(--ink)', marginTop: 3, fontWeight: 500 }} className={k === 'SIRET' ? 'mono' : ''}>{v}</div>
              </div>
            ))}
          </div>
        </div>

        {/* Checklist cohérence */}
        <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--hairline)', display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 13, fontWeight: 600 }}>Check-list PNCEE</span>
            <span className="mono" style={{ fontSize: 11, color: 'var(--ink-4)' }}>auto-calculée</span>
            <span style={{ flex: 1 }} />
            <span className="mono" style={{ fontSize: 11, color: liveScore >= 80 ? 'var(--signal-deep)' : liveScore >= 50 ? 'var(--amber)' : 'var(--rouge)', fontWeight: 600 }}>{liveScore} %</span>
          </div>
          <div>
            {(liveCoherence
              ? liveErrors.length
                ? liveErrors.map((e) => ({ ok: false, label: e.msg || e.code, check: e.code }))
                : [{ ok: true, label: 'Tous les contrôles backend passent', check: 'PNCEE-ready' }]
              : [
                  { ok: true, label: 'AH signée et antérieure au devis', check: 'date AH < date devis' },
                  { ok: true, label: 'SIRET bénéficiaire vérifié', check: 'API INSEE · statut actif' },
                  { ok: true, label: 'Zone climatique cohérente avec adresse', check: `${d.city} → ${d.zone}` },
                  { ok: d.risk > 65, label: 'Attestation RGE valide sur date travaux', check: 'croisement annuaire RGE' },
                  { ok: false, label: 'Facture avec mentions réglementaires', check: 'manque : mention "induit par CEE"' },
                  { ok: true, label: 'Puissance installée cohérente (OCR ↔ déclaré)', check: 'écart < 2 %' },
                ]
            ).map((row, i, arr) => (
              <div key={i} style={{ padding: '9px 16px', borderBottom: i < arr.length - 1 ? '1px solid var(--hairline)' : 'none', display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{
                  width: 18, height: 18, borderRadius: 4,
                  background: row.ok ? 'var(--signal-tint)' : 'var(--rouge-tint)',
                  color: row.ok ? 'var(--signal-deep)' : 'var(--rouge)',
                  display: 'grid', placeItems: 'center',
                  border: `1px solid ${row.ok ? 'var(--signal-soft)' : 'var(--rouge-tint)'}`,
                }}>{row.ok ? <Icon.check /> : <Icon.cross />}</span>
                <span style={{ fontSize: 12, fontWeight: 500, flex: 1 }}>{row.label}</span>
                <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{row.check}</span>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Right: copilot inline + approval chain */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <CopilotInline d={d} />
        <ApprovalChain />
        <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 14 }}>
          <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8 }}>Envoyer pour approbation</div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', lineHeight: 1.5, marginBottom: 10 }}>
            Déclenche la chaîne owner → direction (escalade auto 24 h).
          </div>
          <button
            onClick={onSendForApproval}
            disabled={sendState.loading}
            style={{
              width: '100%', padding: '8px 12px', fontSize: 12, fontWeight: 600,
              background: sendState.loading ? 'var(--paper-3)' : 'var(--signal)',
              color: sendState.loading ? 'var(--ink-4)' : 'var(--ink)',
              border: 'none', borderRadius: 6, cursor: sendState.loading ? 'wait' : 'pointer',
            }}
          >
            {sendState.loading ? 'Envoi en cours…' : 'Envoyer à la chaîne'}
          </button>
          {sendState.result && (
            <div style={{ marginTop: 10, fontSize: 11, padding: 8, background: sendState.result.ok ? 'var(--signal-tint)' : 'var(--rouge-tint)', color: sendState.result.ok ? 'var(--signal-deep)' : 'var(--rouge)', borderRadius: 5 }}>
              {sendState.result.ok
                ? `✓ Approbation créée · ref ${sendState.result.approval?.ref || '—'}`
                : `✗ Échec : ${sendState.result.error || 'voir console'}`}
            </div>
          )}
        </div>
        {/* Dépôt PNCEE — visible quand approved */}
        <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 14 }}>
          <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8 }}>
            🏛️ Dépôt PNCEE / EMMY
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', lineHeight: 1.5, marginBottom: 10 }}>
            {d.stage === 'prime_versee' || d.stage === 'depot_pncee'
              ? 'Dossier approuvé · prêt pour export EMMY (CSV) ou dépôt direct.'
              : 'Le dossier doit être approuvé avant dépôt PNCEE.'}
          </div>
          <button
            onClick={() => {
              window.AE_NAV?.('pncee');
              // Stocke la référence pour pré-sélection dans PNceePage
              try { sessionStorage.setItem('pncee_preselect', d.ref || ''); } catch (_) {}
            }}
            disabled={!['prime_versee', 'depot_pncee', 'approved'].includes(d.stage)}
            style={{
              width: '100%', padding: '8px 12px', fontSize: 12, fontWeight: 600,
              background: ['prime_versee', 'depot_pncee', 'approved'].includes(d.stage) ? 'var(--ink)' : 'var(--paper-3)',
              color: ['prime_versee', 'depot_pncee', 'approved'].includes(d.stage) ? 'var(--paper)' : 'var(--ink-4)',
              border: 'none', borderRadius: 6,
              cursor: ['prime_versee', 'depot_pncee', 'approved'].includes(d.stage) ? 'pointer' : 'not-allowed',
            }}
          >
            📤 Préparer dépôt PNCEE ↗
          </button>
          <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 8 }}>
            Génère un CSV EMMY-compatible · dépôt manuel via portail emmy.fr
          </div>
        </div>

        <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 14 }}>
          <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
            <Icon.lock /> Ledger cumac
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)', lineHeight: 1.5, marginBottom: 8 }}>
            Empreinte unique scellée · non-double-valorisation garantie.
          </div>
          <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', background: 'var(--paper)', padding: 8, borderRadius: 4, border: '1px solid var(--hairline)', wordBreak: 'break-all' }}>
            sha256:9c4a·2f81·{hashStr(d.ref).toString(16)}·d7e0·ab39…
          </div>
          <button style={{ fontSize: 11, color: 'var(--ink-2)', marginTop: 8, fontWeight: 500 }}>Voir chaîne → {DOSSIERS.indexOf(DOSSIERS.find(x=>x.ref===d.ref))+1284}ᵉ bloc</button>
        </div>
      </div>
    </div>
  );
}

function MiniKpi({ label, value, sub, accent, divider }) {
  return (
    <div style={{ padding: 14, borderLeft: divider ? '1px solid var(--line)' : 'none' }}>
      <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 500, display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ width: 4, height: 4, borderRadius: '50%', background: accent }} />
        {label}
      </div>
      <div className="num" style={{ fontSize: 20, fontWeight: 600, letterSpacing: '-0.02em', margin: '4px 0 2px' }}>{value}</div>
      {sub && <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{sub}</div>}
    </div>
  );
}

function CopilotInline({ d }) {
  return (
    <div style={{
      border: '1px solid var(--line)', borderRadius: 8,
      background: 'linear-gradient(135deg, var(--plasma-tint) 0%, var(--paper-2) 60%)',
      overflow: 'hidden',
    }}>
      <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--hairline)', display: 'flex', alignItems: 'center', gap: 8 }}>
        <div style={{ position: 'relative', width: 8, height: 8 }}>
          <span style={{ position: 'absolute', inset: 0, borderRadius: '50%', background: 'var(--plasma)', animation: 'breathe 2.4s infinite' }} />
        </div>
        <span style={{ fontSize: 12, fontWeight: 600 }}>Co-pilote CEE</span>
        <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)', marginLeft: 'auto' }}>claude · 4.5 sonnet</span>
      </div>
      <div style={{ padding: 14, display: 'flex', flexDirection: 'column', gap: 10 }}>
        <div className="fade-up" style={{ fontSize: 12, lineHeight: 1.55, color: 'var(--ink-2)' }}>
          Ce dossier a un score de <strong>{d.risk}/100</strong>. En analysant l'historique,
          <span style={{ background: 'var(--signal-tint)', padding: '0 2px' }}> 3 dossiers similaires </span>
          (même FOST, même zone, même délégataire) ont été <strong>rejetés en 2025</strong> pour un motif récurrent :
          mention "induit par CEE" absente sur la facture.
        </div>
        <div className="fade-up" style={{ animationDelay: '250ms', display: 'flex', flexDirection: 'column', gap: 6 }}>
          {[
            { label: 'Générer l\'AH manquante', k: 'AH' },
            { label: 'Régénérer la facture avec mention', k: 'FA' },
            { label: 'Lancer l\'audit inversé complet', k: 'AI' },
          ].map((a, i) => (
            <button key={a.k} style={{
              display: 'flex', alignItems: 'center', gap: 8,
              padding: '8px 10px', background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 6,
              fontSize: 12, fontWeight: 500, color: 'var(--ink-2)', textAlign: 'left',
            }}>
              <span className="mono" style={{ fontSize: 9, padding: '1px 4px', background: 'var(--plasma-tint)', color: 'var(--plasma)', borderRadius: 3, fontWeight: 600 }}>{a.k}</span>
              <span style={{ flex: 1 }}>{a.label}</span>
              <Icon.arrow style={{ color: 'var(--ink-4)' }} />
            </button>
          ))}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 10, color: 'var(--ink-4)', paddingTop: 4, borderTop: '1px solid var(--hairline)' }}>
          <span>Demander autre chose…</span>
          <span style={{ flex: 1 }} />
          <span className="mono" style={{ padding: '1px 4px', border: '1px solid var(--line-2)', borderRadius: 3 }}>/</span>
        </div>
      </div>
    </div>
  );
}

function ApprovalChain() {
  return (
    <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
      <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--hairline)' }}>
        <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 2 }}>Chaîne d'approbation</div>
        <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>escalade auto 24 h si sans réponse</div>
      </div>
      <div style={{ padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        <ApprovalRow level="Owner" name="nils.bazin@" status="approved" at="J+0 · 11:42" />
        <ApprovalRow level="Direction" name="direction@" status="pending" timer={14*3600 + 22*60} />
      </div>
    </div>
  );
}

function ApprovalRow({ level, name, status, at, timer }) {
  const [t, setT] = useState(timer);
  useEffect(() => {
    if (status !== 'pending') return;
    const id = setInterval(() => setT(v => Math.max(0, v - 1)), 1000);
    return () => clearInterval(id);
  }, [status]);
  const hh = Math.floor(t / 3600);
  const mm = Math.floor((t % 3600) / 60);
  const ss = t % 60;
  const frac = status === 'pending' ? t / (24 * 3600) : 1;
  const colour = status === 'approved' ? 'var(--signal-deep)' : 'var(--amber)';

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
      <div style={{ position: 'relative', width: 34, height: 34 }}>
        <svg width="34" height="34">
          <circle cx="17" cy="17" r="14" fill="none" stroke="var(--paper-3)" strokeWidth="2" />
          <circle cx="17" cy="17" r="14" fill="none" stroke={colour} strokeWidth="2" strokeDasharray={88} strokeDashoffset={88 - 88 * frac} transform="rotate(-90 17 17)" strokeLinecap="round" />
        </svg>
        <span style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', fontSize: 11 }}>
          {status === 'approved' ? <span style={{ color: colour }}><Icon.check /></span> : <span style={{ fontSize: 9 }} className="mono">{hh}h</span>}
        </span>
      </div>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 12, fontWeight: 600 }}>{level}</div>
        <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>
          {status === 'approved' && <>approuvé · {at}</>}
          {status === 'pending' && <>en attente · <span className="mono">{String(hh).padStart(2,'0')}:{String(mm).padStart(2,'0')}:{String(ss).padStart(2,'0')}</span> restantes</>}
        </div>
        <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 1 }} className="mono">{name}</div>
      </div>
      {status === 'pending' && <button style={{ fontSize: 11, color: 'var(--ink-2)', fontWeight: 500, padding: '4px 8px', border: '1px solid var(--line-2)', borderRadius: 4 }}>Escalader</button>}
    </div>
  );
}

function DossierPieces({ d }) {
  const pieces = [
    { name: 'AH_beneficiaire_v3.pdf', kind: 'AH', ocr: 'ok', confidence: 98, extracted: 'Date visite : 12/03/2026 · Signataire : J. Mirabeau' },
    { name: 'Devis_DUC-2026-0042.pdf', kind: 'Devis', ocr: 'ok', confidence: 96, extracted: 'Montant HT : 24 680 € · Date : 08/03/2026' },
    { name: 'Facture_F-2026-0188.pdf', kind: 'Facture', ocr: 'warn', confidence: 82, extracted: 'Mention CEE manquante · puissance détectée : 14 kW' },
    { name: 'RGE_QualiPAC_2026.pdf', kind: 'RGE', ocr: 'ok', confidence: 100, extracted: 'Valide jusqu\'au 31/12/2026 · n° QP-147-221' },
    { name: 'Photo_installation_01.jpg', kind: 'Photo', ocr: 'ok', confidence: 94, extracted: 'Modèle détecté : Daikin EHBH04EA · pompe à chaleur air/eau' },
  ];
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 380px', gap: 16 }}>
      <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', overflow: 'hidden' }}>
        <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--hairline)', display: 'flex', alignItems: 'center', gap: 8 }}>
          <span style={{ fontSize: 13, fontWeight: 600 }}>Pièces justificatives</span>
          <Badge tone="plasma"><Icon.spark /> OCR Claude Vision</Badge>
          <span style={{ flex: 1 }} />
          <Btn variant="outline" size="sm" icon={<Icon.plus />}>Ajouter</Btn>
        </div>
        {pieces.map((p, i) => (
          <div key={i} style={{ padding: '12px 16px', borderBottom: i < pieces.length - 1 ? '1px solid var(--hairline)' : 'none', display: 'grid', gridTemplateColumns: '28px 1fr auto', gap: 12, alignItems: 'center' }}>
            <div style={{ width: 28, height: 36, border: '1px solid var(--line)', borderRadius: 3, background: 'var(--paper)', display: 'grid', placeItems: 'center' }}>
              <Icon.doc style={{ color: 'var(--ink-4)' }} />
            </div>
            <div>
              <div style={{ fontSize: 12, fontWeight: 600 }} className="mono">{p.name}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>
                <Badge tone={p.ocr === 'ok' ? 'signal' : 'amber'}>{p.kind}</Badge>
                <span style={{ marginLeft: 8 }}>{p.extracted}</span>
              </div>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <div style={{ textAlign: 'right' }}>
                <div className="num" style={{ fontSize: 11, fontWeight: 600, color: p.confidence > 90 ? 'var(--signal-deep)' : 'var(--amber)' }}>{p.confidence} %</div>
                <div style={{ fontSize: 9, color: 'var(--ink-4)' }}>confiance</div>
              </div>
              <button style={{ padding: 4, color: 'var(--ink-4)' }}><Icon.eye /></button>
            </div>
          </div>
        ))}
      </div>

      <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 14 }}>
        <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10 }}>Champs extraits (structured output)</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 11 }}>
          {[
            ['Date AH', '12/03/2026', true],
            ['Date devis', '08/03/2026', false, 'AH postérieure au devis ↯'],
            ['SIRET installateur', '784 221 109 000 22', true],
            ['Marque équipement', 'Daikin', true],
            ['Modèle', 'EHBH04EA', true],
            ['Puissance', '14 kW', true],
            ['COP saisonnier', '4,2', true],
            ['Mention "induit par CEE"', 'absente', false],
          ].map(([k, v, ok, warn], i) => (
            <div key={i} style={{ display: 'grid', gridTemplateColumns: '120px 1fr auto', gap: 8, padding: '6px 0', borderBottom: '1px solid var(--hairline)' }}>
              <span style={{ color: 'var(--ink-4)' }}>{k}</span>
              <span className="mono" style={{ color: ok ? 'var(--ink)' : 'var(--rouge)' }}>{v}</span>
              <span>{ok ? <Icon.check style={{ color: 'var(--signal-deep)' }} /> : <Icon.cross style={{ color: 'var(--rouge)' }} />}</span>
              {warn && <span style={{ gridColumn: '1 / -1', fontSize: 10, color: 'var(--copper)' }}>{warn}</span>}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function DossierCalcul({ d }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
      <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 20 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>Formule {d.fost}</div>
        <div style={{ fontSize: 11, color: 'var(--ink-4)', marginBottom: 16 }}>arrêté du 22/12/2014 · version consolidée 04/2026</div>
        <div style={{ padding: 20, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 13, lineHeight: 1.9 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ color: 'var(--ink-4)' }}>kWh_cumac</span>
            <span>=</span>
            <span style={{ color: 'var(--signal-deep)' }}>M</span>
            <span>×</span>
            <span style={{ color: 'var(--signal-deep)' }}>P</span>
            <span>×</span>
            <span style={{ color: 'var(--signal-deep)' }}>Z</span>
            <span>×</span>
            <span style={{ color: 'var(--plasma)' }}>C</span>
          </div>
          <div style={{ marginTop: 14, fontSize: 11, color: 'var(--ink-3)' }}>
            <div>M · montant forfaitaire = <strong style={{ color: 'var(--ink)' }}>28 000</strong> kWh/kW</div>
            <div>P · puissance thermique = <strong style={{ color: 'var(--ink)' }}>14</strong> kW <span style={{ color: 'var(--ink-4)' }}>(OCR facture)</span></div>
            <div>Z · coeff zone {d.zone} = <strong style={{ color: 'var(--ink)' }}>1,1</strong></div>
            <div>C · coup de pouce précarité = <strong style={{ color: 'var(--plasma)' }}>×3</strong> <span style={{ color: 'var(--ink-4)' }}>(bénéficiaire modeste)</span></div>
          </div>
          <div style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--line)', display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <span style={{ color: 'var(--ink-4)' }}>=</span>
            <span className="num" style={{ fontSize: 22, fontWeight: 600, color: 'var(--signal-deep)' }}>{fmtMWh(d.cumac)}</span>
          </div>
        </div>
      </div>

      <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 20 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 16 }}>Simulateur de scénarios</div>
        {[
          { label: 'Base', cumac: d.cumac / 3, amount: d.amount / 3, active: false },
          { label: '+ coup de pouce précarité', cumac: d.cumac, amount: d.amount, active: true },
          { label: '+ bonification territoire', cumac: d.cumac * 1.25, amount: d.amount * 1.25, active: false },
          { label: '+ fiche complémentaire BAR-EN-101', cumac: d.cumac * 1.5, amount: d.amount * 1.6, active: false },
        ].map((s, i) => (
          <div key={i} style={{
            display: 'grid', gridTemplateColumns: '1fr 90px 80px', gap: 10, alignItems: 'center',
            padding: '10px 12px', marginBottom: 6,
            background: s.active ? 'var(--signal-tint)' : 'var(--paper)',
            border: `1px solid ${s.active ? 'var(--signal-soft)' : 'var(--line)'}`, borderRadius: 5,
          }}>
            <div>
              <div style={{ fontSize: 12, fontWeight: 500 }}>{s.label}</div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 1 }}>{s.active ? 'actif' : 'disponible'}</div>
            </div>
            <div className="num" style={{ fontSize: 13, fontWeight: 600, textAlign: 'right' }}>{fmtMWh(s.cumac)}</div>
            <div className="num" style={{ fontSize: 11, color: s.active ? 'var(--signal-deep)' : 'var(--ink-4)', textAlign: 'right' }}>{fmtEur(s.amount)}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

function DossierSignatures({ d, onOpenSignature }) {
  const sigs = [
    { who: 'J. Mirabeau (bénéficiaire)', doc: 'AH_beneficiaire_v3.pdf', status: 'signed', at: '12/03/2026 14:22', ip: '82.66.147.203', hash: hashStr(d.ref + 'j').toString(16) },
    { who: 'T. Villar (installateur RGE)', doc: 'Attestation_RGE.pdf', status: 'signed', at: '11/03/2026 09:08', ip: '193.55.78.11', hash: hashStr(d.ref + 't').toString(16) },
    { who: 'Direction A-E', doc: 'Validation_dossier_final.pdf', status: 'pending', at: null },
  ];
  return (
    <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
      <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--hairline)', display: 'flex', alignItems: 'center', gap: 8 }}>
        <span style={{ fontSize: 13, fontWeight: 600 }}>Signature électronique native</span>
        <Badge tone="signal"><Icon.shield /> eIDAS simple · horodatage serveur</Badge>
        <span style={{ flex: 1 }} />
        <Btn variant="signal" size="sm" onClick={onOpenSignature}>Nouvelle demande</Btn>
      </div>
      {sigs.map((s, i) => (
        <div key={i} style={{ padding: '14px 16px', borderBottom: i < sigs.length - 1 ? '1px solid var(--hairline)' : 'none', display: 'grid', gridTemplateColumns: '30px 1fr auto', gap: 12, alignItems: 'center' }}>
          <div style={{ width: 30, height: 30, borderRadius: '50%', background: s.status === 'signed' ? 'var(--signal-tint)' : 'var(--paper-3)', display: 'grid', placeItems: 'center', color: s.status === 'signed' ? 'var(--signal-deep)' : 'var(--ink-4)' }}>
            {s.status === 'signed' ? <Icon.check /> : <Icon.sign />}
          </div>
          <div>
            <div style={{ fontSize: 12, fontWeight: 600 }}>{s.who}</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)' }} className="mono">{s.doc}</div>
            {s.status === 'signed' && (
              <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 3 }}>
                signé le {s.at} · IP <span className="mono">{s.ip}</span> · hash <span className="mono">{s.hash.slice(0,12)}…</span>
              </div>
            )}
            {s.status === 'pending' && <div style={{ fontSize: 10, color: 'var(--amber)', marginTop: 3 }}>en attente de signature</div>}
          </div>
          <div>
            {s.status === 'signed' ? <Btn variant="outline" size="sm">Preuve légale</Btn> : <Btn variant="signal" size="sm" onClick={onOpenSignature}>Relancer</Btn>}
          </div>
        </div>
      ))}
    </div>
  );
}

function DossierTimeline({ d }) {
  const events = [
    { t: 'J+0', at: '08/02', who: 'Odoo', label: 'Opportunité créée', detail: 'kind=quote · lead qualifié' },
    { t: 'J+3', at: '11/02', who: 'T. Aubert', label: 'Pré-visite planifiée', detail: 'PV-2026-014 · mixed' },
    { t: 'J+8', at: '16/02', who: 'TechVisit', label: 'Visite technique', detail: 'VT-2026-011 · 23 équipements détectés par Claude Vision' },
    { t: 'J+11', at: '19/02', who: 'Co-pilote', label: 'Score initial calculé', detail: '68 / 100 · 2 anomalies' },
    { t: 'J+14', at: '22/02', who: 'M. Ferrand', label: 'Devis envoyé', detail: '24 680 € HT' },
    { t: 'J+19', at: '27/02', who: 'Client', label: 'Devis signé', detail: 'signature native · eIDAS simple' },
    { t: 'J+28', at: '08/03', who: 'Installateur', label: 'Travaux terminés', detail: 'facture uploadée · OCR 94 %' },
    { t: 'J+30', at: '10/03', who: 'Co-pilote', label: 'Anomalie détectée', detail: 'mention CEE absente sur facture', tone: 'copper' },
    { t: 'J+31', at: '11/03', who: 'Vous', label: 'Dossier en constitution', detail: 'actuellement', tone: 'signal' },
  ];
  return (
    <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: '18px 24px' }}>
      <div style={{ position: 'relative', paddingLeft: 24 }}>
        <div style={{ position: 'absolute', left: 6, top: 4, bottom: 4, width: 1, background: 'var(--line-2)' }} />
        {events.map((e, i) => (
          <div key={i} style={{ position: 'relative', paddingBottom: 18 }}>
            <div style={{ position: 'absolute', left: -22, top: 2, width: 12, height: 12, borderRadius: '50%', background: e.tone === 'copper' ? 'var(--copper)' : e.tone === 'signal' ? 'var(--signal)' : 'var(--paper-3)', border: '2px solid var(--paper-2)' }} />
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
              <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)', width: 54 }}>{e.t} · {e.at}</span>
              <div>
                <div style={{ fontSize: 12, fontWeight: 600 }}>{e.label}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{e.detail} <span style={{ color: 'var(--ink-4)' }}>· {e.who}</span></div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function DossierAudit({ d }) {
  const [etudeOpen, setEtudeOpen] = useState(false);
  const [etudes, setEtudes] = useState([]);
  useEffect(() => {
    let cancelled = false;
    const api = (window.AE_API && window.AE_API.BASE) || '';
    fetch(`${api}/api/thermique-reglementaire/etudes`)
      .then(r => r.json())
      .then(list => {
        if (!cancelled && Array.isArray(list)) {
          // filtre sur ceeDossierId si le dossier a un id backend
          const related = d.id ? list.filter(e => e.ceeDossierId === d.id) : list.slice(0, 5);
          setEtudes(related);
        }
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [d.ref]);
  return (
    <div style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 20 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
        <Icon.shield style={{ color: 'var(--plasma)' }} />
        <div>
          <div style={{ fontSize: 14, fontWeight: 600 }}>Mode audit inversé</div>
          <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Le co-pilote simule un contrôle PNCEE et liste les points faibles.</div>
        </div>
        <span style={{ flex: 1 }} />
        <Btn variant="outline" size="sm" icon={<Icon.refresh />}>Relancer</Btn>
        <Btn variant="solid" size="sm" onClick={() => setEtudeOpen(true)}>🧪 Étude thermique</Btn>
      </div>

      {/* Études thermiques liées */}
      {etudes.length > 0 && (
        <div style={{ marginBottom: 16, padding: 12, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 6 }}>
          <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 8 }}>
            Études thermiques ({etudes.length})
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {etudes.map(e => {
              const c = e.resultats?.classes?.classeFinale || '?';
              const cep = e.resultats?.indicateurs?.cepSpecifique_kWhEP_m2;
              return (
                <div key={e.id} style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto auto auto', gap: 10, padding: '6px 10px', fontSize: 11, alignItems: 'center', border: '1px solid var(--hairline)', borderRadius: 5 }}>
                  <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{e.ref}</span>
                  <span style={{ fontWeight: 500 }}>{e.clientName || '—'}</span>
                  <span style={{ display: 'inline-flex', padding: '2px 8px', borderRadius: 10, background: '#00a651', color: '#fff', fontSize: 10, fontWeight: 700 }}>{c}</span>
                  <span className="mono" style={{ fontSize: 10 }}>{cep ? Math.round(cep) + ' kWhEP/m²' : '—'}</span>
                  <span className="mono" style={{ fontSize: 9, color: 'var(--ink-4)' }}>{e.status}</span>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {etudeOpen && window.EtudeReglementaireModal && (
        <window.EtudeReglementaireModal
          onClose={() => setEtudeOpen(false)}
          dossier={d}
        />
      )}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 260px', gap: 20 }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {[
            { sev: 'rouge', title: 'Mention CEE absente sur facture', impact: '-18 pts', remedy: 'Demander avoir + facture corrigée' },
            { sev: 'amber', title: 'AH signée 4 jours avant devis (écart limite)', impact: '-6 pts', remedy: 'Vérifier que la pré-visite est antérieure à l\'AH' },
            { sev: 'amber', title: 'COP déclaré légèrement inférieur au seuil FOST', impact: '-4 pts', remedy: 'Croiser avec fiche EPREL produit' },
            { sev: 'signal', title: 'Zone climatique et coefficient alignés', impact: '+0', remedy: null },
          ].map((r, i) => (
            <div key={i} style={{ padding: 14, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 6, display: 'grid', gridTemplateColumns: '8px 1fr 70px', gap: 12, alignItems: 'center' }}>
              <Dot tone={r.sev} size={8} />
              <div>
                <div style={{ fontSize: 12, fontWeight: 600 }}>{r.title}</div>
                {r.remedy && <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>Remédiation : {r.remedy}</div>}
              </div>
              <span className="num" style={{ fontSize: 13, fontWeight: 600, color: r.sev === 'rouge' ? 'var(--rouge)' : r.sev === 'amber' ? 'var(--amber)' : 'var(--signal-deep)', textAlign: 'right' }}>{r.impact}</span>
            </div>
          ))}
        </div>
        <div>
          <div style={{ textAlign: 'center', padding: 20, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 8 }}>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 500 }}>Si déposé maintenant</div>
            <div className="num" style={{ fontSize: 48, fontWeight: 600, color: 'var(--amber)', lineHeight: 1, margin: '8px 0' }}>{d.risk}</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>acceptation probable · 63 %</div>
          </div>
          <div style={{ textAlign: 'center', padding: 20, background: 'var(--paper)', border: '1px solid var(--signal-soft)', borderRadius: 8, marginTop: 10 }}>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 500 }}>Après remédiation</div>
            <div className="num" style={{ fontSize: 48, fontWeight: 600, color: 'var(--signal-deep)', lineHeight: 1, margin: '8px 0' }}>94</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>acceptation probable · 96 %</div>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── DossierAppointments — RDV liés au dossier CEE (règle n°3) ───────────────
function DossierAppointments({ d }) {
  const API = (window.AE_API && window.AE_API.BASE) || '';
  const [appts, setAppts] = useState([]);
  const [loading, setLoading] = useState(false);
  const [oppId, setOppId] = useState(null);

  // Le dossier est lié à une opportunité (d.opportunityId direct si AE, sinon
  // résolution via /api/cee/drafts ou ref).
  const reload = async () => {
    setLoading(true);
    try {
      // 1. Trouver l'opportunityId : direct depuis d, sinon via /api/cee/drafts
      let resolvedOpp = d.opportunityId || null;
      let directDossierId = d.id || null;
      if (!resolvedOpp && d.ref) {
        const drafts = await fetch(`${API}/api/cee/drafts?ref=${encodeURIComponent(d.ref)}`).then(x => x.json()).catch(() => null);
        const draft = drafts?.drafts?.[0] || drafts?.draft || null;
        if (draft?.opportunityId) resolvedOpp = draft.opportunityId;
        if (draft?.id && !directDossierId) directDossierId = draft.id;
      }
      setOppId(resolvedOpp);

      // 2. Récupérer les RDV liés (par opportunityId OU par notes contenant la ref dossier)
      const r = await fetch(`${API}/api/calendar/appointments`).then(x => x.json());
      if (!r.ok) { setLoading(false); return; }
      const all = r.appointments || [];
      const linked = all.filter(a => {
        if (resolvedOpp && a.opportunityId === resolvedOpp) return true;
        // Fallback : référence dossier dans notes/title
        if (d.ref && [(a.title || ''), (a.notes || '')].some(s => s.includes(d.ref))) return true;
        return false;
      });
      setAppts(linked);
    } catch (_) {}
    setLoading(false);
  };
  useEffect(() => { reload(); }, [d?.id, d?.ref]);

  const KIND_LABEL = {
    rdv_commercial: '💼 RDV commercial',
    visite_technique: '🔧 Visite technique',
    pre_visite: '📅 Pré-visite',
    relance: '📞 Relance',
    deadline: '⏰ Deadline',
    signature: '✍ Signature',
    autre: '📌 Autre',
  };

  const openInCalendar = (a) => {
    try { window.AE_NOTIF_TARGET = { kind: 'appointment', id: a.id, ref: a.title }; } catch (_) {}
    if (window.AE_NAV) window.AE_NAV('calendar');
  };

  const createNew = async () => {
    if (!oppId) return alert('Ce dossier n\'est pas encore lié à une opportunité — créez-en une d\'abord depuis le CRM.');
    const date = window.prompt('Date du RDV (AAAA-MM-JJ HH:MM)', new Date(Date.now() + 86400_000).toISOString().slice(0, 16).replace('T', ' '));
    if (!date) return;
    const dt = new Date(date.replace(' ', 'T'));
    if (isNaN(dt.getTime())) return alert('Date invalide');
    try {
      const r = await fetch(`${API}/api/calendar/appointments`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          title: `Dossier ${d.ref} — RDV`,
          kind: 'rdv_commercial',
          scheduledAt: dt.toISOString(),
          durationMin: 60,
          opportunityId: oppId,
          clientName: d.client || null,
          notes: `Lié au dossier CEE ${d.ref}`,
        }),
      }).then(x => x.json());
      if (r.ok) reload();
    } catch (_) {}
  };

  const upcoming = appts.filter(a => new Date(a.scheduledAt) >= new Date() && a.status === 'scheduled');
  const past = appts.filter(a => new Date(a.scheduledAt) < new Date() || a.status !== 'scheduled');

  return (
    <div style={{ padding: '4px 0' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
        <span style={{ fontSize: 14, fontWeight: 600 }}>📅 RDV liés à ce dossier</span>
        <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
          {appts.length} RDV · {upcoming.length} à venir{oppId ? ` · opp #${oppId}` : ' · pas d\'opp liée'}
        </span>
        <span style={{ flex: 1 }} />
        <button onClick={reload} style={{ fontSize: 10, color: 'var(--ink-4)', padding: '2px 6px' }}>{loading ? '…' : '⟳'}</button>
        <button onClick={createNew} disabled={!oppId}
          style={{ padding: '5px 12px', fontSize: 12, border: '1px solid var(--line)', borderRadius: 5, background: oppId ? 'var(--ink)' : 'var(--paper-3)', color: oppId ? 'var(--paper)' : 'var(--ink-5)', fontWeight: 500, opacity: oppId ? 1 : 0.6 }}>
          + Nouveau RDV
        </button>
      </div>

      {appts.length === 0 ? (
        <div style={{ padding: '24px 16px', textAlign: 'center', fontSize: 12, color: 'var(--ink-4)', border: '1px dashed var(--line)', borderRadius: 8 }}>
          {oppId
            ? 'Aucun RDV lié à ce dossier. Cliquez "+ Nouveau RDV" ou passez l\'opportunité au stage audit_planifie pour création auto.'
            : 'Ce dossier n\'a pas d\'opportunité associée. Liez-le à une opportunité depuis le CRM pour pouvoir créer des RDV.'}
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {upcoming.length > 0 && (
            <div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 6 }}>À venir ({upcoming.length})</div>
              {upcoming.map(a => (
                <button key={a.id} onClick={() => openInCalendar(a)}
                  style={{
                    display: 'grid', gridTemplateColumns: '20px 1fr auto auto', gap: 12, alignItems: 'center',
                    padding: '10px 14px', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper)',
                    textAlign: 'left', cursor: 'pointer', fontSize: 12, marginBottom: 6, width: '100%',
                  }}>
                  <span style={{ width: 10, height: 10, borderRadius: '50%', background: a.color || 'var(--signal)' }} />
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink)', lineHeight: 1.3 }}>{a.title}</div>
                    <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
                      {KIND_LABEL[a.kind] || a.kind} · {a.durationMin || 60} min
                      {a.location ? ` · ${a.location}` : ''}
                      {a.assignedTo ? ` · ${a.assignedTo}` : ''}
                    </div>
                  </div>
                  <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>
                    {new Date(a.scheduledAt).toLocaleDateString('fr-FR', { weekday: 'short', day: '2-digit', month: 'short' })}
                    {' '}{new Date(a.scheduledAt).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
                  </span>
                  <span style={{ color: 'var(--ink-5)', fontSize: 12 }}>→</span>
                </button>
              ))}
            </div>
          )}

          {past.length > 0 && (
            <details>
              <summary style={{ fontSize: 11, color: 'var(--ink-4)', cursor: 'pointer', padding: '6px 0' }}>
                ▸ {past.length} RDV passé{past.length > 1 ? 's' : ''} ou annulé{past.length > 1 ? 's' : ''}
              </summary>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 6 }}>
                {past.map(a => (
                  <button key={a.id} onClick={() => openInCalendar(a)}
                    style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, padding: '7px 10px', border: '1px solid var(--hairline)', borderRadius: 5, background: 'transparent', textAlign: 'left', fontSize: 11, color: 'var(--ink-3)' }}>
                    <span>{a.title}</span>
                    <span className="mono">{new Date(a.scheduledAt).toLocaleDateString('fr-FR')} · {a.status}</span>
                  </button>
                ))}
              </div>
            </details>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Portail client — gestion des accès et visibilité ────────────────────────
function DossierPortal({ d }) {
  const API = (window.AE_API && window.AE_API.BASE) || '';
  const [draftId, setDraftId]     = useState(null);
  const [loading, setLoading]     = useState(true);
  const [visibility, setVis]      = useState(null);
  const [tokens, setTokens]       = useState([]);
  const [generating, setGenerating] = useState(false);
  const [newLink, setNewLink]     = useState(null); // { url, expiresAt, emailSent } — affiché une seule fois
  const [copied, setCopied]       = useState(false);
  const [sendEmail, setSendEmail] = useState(false);
  const [savingVis, setSavingVis] = useState(null); // key en cours de save

  const SECTIONS = [
    { key: 'summary',     icon: '📋', label: 'Résumé du dossier',          desc: 'Référence, statut, FOST, prime estimée' },
    { key: 'checklist',   icon: '✅', label: 'Étapes & pièces à fournir',  desc: 'Avancement et liste des pièces requises' },
    { key: 'documents',   icon: '📁', label: 'Documents partagés',         desc: 'Fichiers uploadés sur le dossier' },
    { key: 'signatures',  icon: '✍️', label: 'Signatures électroniques',   desc: 'Statut des documents à signer' },
    { key: 'invoices',    icon: '💶', label: 'Facturation',                 desc: 'Devis et factures liés' },
    { key: 'obligations', icon: '⚡', label: 'Obligations BACS / Tertiaire', desc: 'Calendrier réglementaire et compte à rebours OPERAT' },
  ];

  useEffect(() => {
    fetch(`${API}/api/cee/drafts?limit=200`).then(r => r.json()).then(res => {
      const all = res?.drafts || [];
      const hit = all.find(x =>
        (x.clientName && x.clientName.toLowerCase() === (d.client || '').toLowerCase()) ||
        (x.fostCode && x.fostCode === d.fost)
      );
      if (hit) { setDraftId(hit.id); reload(hit.id); }
      else setLoading(false);
    }).catch(() => setLoading(false));
  }, [d.ref]);

  const reload = async (id) => {
    const [visRes, tokRes] = await Promise.all([
      fetch(`${API}/api/client-portal/visibility/${id}`).then(r => r.json()).catch(() => null),
      fetch(`${API}/api/client-portal/tokens/${id}`).then(r => r.json()).catch(() => null),
    ]);
    if (visRes?.ok) setVis(visRes.portalVisibility);
    if (tokRes?.ok) setTokens(tokRes.tokens || []);
    setLoading(false);
  };

  const toggle = async (key, val) => {
    if (!draftId || !visibility) return;
    const prev = visibility;
    setVis({ ...prev, [key]: val });
    setSavingVis(key);
    await fetch(`${API}/api/client-portal/visibility/${draftId}`, {
      method: 'PATCH', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ [key]: val }),
    }).catch(() => setVis(prev));
    setSavingVis(null);
  };

  const generate = async () => {
    if (!draftId) return;
    setGenerating(true); setNewLink(null);
    const r = await fetch(`${API}/api/client-portal/invite`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ dossierId: draftId, sendInvite: sendEmail }),
    }).then(r => r.json()).catch(() => null);
    setGenerating(false);
    if (r?.ok) { setNewLink({ url: r.portalUrl, expiresAt: r.expiresAt, emailSent: r.emailSent }); reload(draftId); }
  };

  const revoke = async (tokenId) => {
    if (!confirm('Désactiver ce lien ? Le client ne pourra plus y accéder.')) return;
    await fetch(`${API}/api/client-portal/tokens/${tokenId}`, { method: 'DELETE' });
    reload(draftId);
  };

  const copy = (text) => {
    navigator.clipboard.writeText(text).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2500); });
  };

  const fmtDate = (s) => s ? new Date(s).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' }) : '—';
  const fmtRelative = (s) => {
    if (!s) return '—';
    const diff = Date.now() - new Date(s).getTime();
    const min = Math.floor(diff / 60000);
    if (min < 1)    return 'à l\'instant';
    if (min < 60)   return `il y a ${min} min`;
    const h = Math.floor(min / 60);
    if (h < 24)     return `il y a ${h} h`;
    const j = Math.floor(h / 24);
    if (j < 30)     return `il y a ${j} j`;
    return new Date(s).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
  };

  const lblS = { fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 3 };

  if (loading) return (
    <div style={{ padding: 40, textAlign: 'center', fontSize: 12, color: 'var(--ink-4)' }}>
      <div style={{ marginBottom: 8 }}>Chargement…</div>
    </div>
  );

  if (!draftId) return (
    <div style={{ padding: 32, maxWidth: 480 }}>
      <div style={{ padding: '14px 18px', background: 'var(--amber-tint)', border: '1px solid var(--amber)', borderRadius: 8, fontSize: 12, color: 'var(--ink-2)' }}>
        <strong>Dossier non synchronisé</strong><br />
        Ce dossier n'est pas encore enregistré en base. Enregistrez-le d'abord via "Éditer dossier" pour activer le portail client.
      </div>
    </div>
  );

  const activeTokens  = tokens.filter(t => !t.revokedAt && new Date(t.expiresAt) > new Date());
  const expiredTokens = tokens.filter(t => t.revokedAt || new Date(t.expiresAt) <= new Date());
  // lastUsedAt le plus récent tous tokens confondus + nb de consultations
  const lastSeenAt = tokens.reduce((acc, t) =>
    t.lastUsedAt && (!acc || new Date(t.lastUsedAt) > new Date(acc)) ? t.lastUsedAt : acc, null);
  const seenCount  = tokens.filter(t => t.lastUsedAt).length;

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 340px', gap: 20, alignItems: 'flex-start' }}>
      {/* ── Bannière activité client (full-width au-dessus des deux cols) ── */}
      {(lastSeenAt || tokens.length > 0) && (
        <div style={{
          gridColumn: '1 / -1',
          display: 'flex', alignItems: 'center', gap: 12,
          padding: '10px 16px', borderRadius: 8,
          background: lastSeenAt ? 'var(--signal-tint)' : 'var(--paper-2)',
          border: `1px solid ${lastSeenAt ? 'var(--signal-soft)' : 'var(--line)'}`,
        }}>
          <span style={{
            width: 8, height: 8, borderRadius: '50%',
            background: lastSeenAt ? 'var(--signal)' : 'var(--ink-5)',
            flexShrink: 0,
            boxShadow: lastSeenAt ? '0 0 0 4px rgba(63,224,124,0.18)' : 'none',
          }}/>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink)' }}>
              {lastSeenAt
                ? `Le client a consulté son espace ${fmtRelative(lastSeenAt)}`
                : 'Le client n\'a pas encore consulté son espace'}
            </div>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 1 }}>
              {tokens.length} lien(s) émis · {activeTokens.length} actif(s) · {seenCount} consultation(s)
            </div>
          </div>
        </div>
      )}

      {/* ── Colonne gauche : visibilité ── */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>

        {/* Header bloc visibilité */}
        <div style={{ border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--line)', background: 'var(--paper-2)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
            <div>
              <div style={{ fontSize: 13, fontWeight: 600 }}>Contenu visible par le client</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>Activez uniquement ce que vous souhaitez partager.</div>
            </div>
            <span style={{ fontSize: 10, padding: '3px 8px', background: 'var(--signal-tint)', color: 'var(--signal-deep)', border: '1px solid var(--signal-soft)', borderRadius: 999, fontWeight: 600 }}>
              {visibility ? Object.values(visibility).filter(Boolean).length : 0} / {SECTIONS.length} sections
            </span>
          </div>

          <div style={{ background: 'var(--paper)' }}>
            {SECTIONS.map((s, i) => {
              const on = visibility?.[s.key] ?? false;
              const saving = savingVis === s.key;
              return (
                <div key={s.key} style={{
                  display: 'grid', gridTemplateColumns: '1fr auto',
                  alignItems: 'center', gap: 16,
                  padding: '13px 16px',
                  borderBottom: i < SECTIONS.length - 1 ? '1px solid var(--hairline)' : 'none',
                  transition: 'background 120ms',
                  background: on ? 'transparent' : 'transparent',
                }}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
                    <span style={{ fontSize: 16, lineHeight: 1, marginTop: 1, opacity: on ? 1 : 0.4 }}>{s.icon}</span>
                    <div>
                      <div style={{ fontSize: 13, fontWeight: 500, color: on ? 'var(--ink)' : 'var(--ink-3)' }}>{s.label}</div>
                      <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 1 }}>{s.desc}</div>
                    </div>
                  </div>
                  {/* Toggle pill */}
                  <button
                    onClick={() => toggle(s.key, !on)}
                    disabled={saving}
                    title={on ? 'Masquer au client' : 'Rendre visible'}
                    style={{
                      width: 36, height: 20, borderRadius: 999, border: 'none', padding: 2,
                      background: on ? 'var(--signal)' : 'var(--paper-3)',
                      cursor: saving ? 'default' : 'pointer',
                      transition: 'background 200ms',
                      position: 'relative', flexShrink: 0,
                      outline: 'none',
                    }}
                  >
                    <span style={{
                      display: 'block', width: 16, height: 16, borderRadius: '50%',
                      background: on ? '#0B1F12' : 'var(--ink-4)',
                      transition: 'transform 200ms',
                      transform: on ? 'translateX(16px)' : 'translateX(0)',
                    }} />
                  </button>
                </div>
              );
            })}
          </div>
        </div>

        {/* Info sécurité */}
        <div style={{ padding: '11px 14px', background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 8, fontSize: 11, color: 'var(--ink-4)', lineHeight: 1.6 }}>
          <span style={{ fontWeight: 600, color: 'var(--ink-3)' }}>Sécurité · </span>
          Le lien magic-link est personnel et expire dans 30 jours. Le token est hashé en base (SHA-256) — il n'est jamais stocké en clair. Vous pouvez le révoquer à tout moment.
        </div>
      </div>

      {/* ── Colonne droite : génération + tokens ── */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>

        {/* Génération lien */}
        <div style={{ border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
          <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--line)', background: 'var(--paper-2)' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <div style={{ fontSize: 13, fontWeight: 600 }}>Générer un lien d'accès</div>
              <button
                onClick={async () => {
                  if (!draftId) return;
                  const r = await fetch(`${API}/api/client-portal/invite`, {
                    method: 'POST', headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ dossierId: draftId, preview: true }),
                  }).then(r => r.json()).catch(() => null);
                  if (r?.ok) window.open(r.portalUrl, '_blank');
                }}
                style={{ fontSize: 11, padding: '4px 10px', background: 'var(--paper-3)', color: 'var(--ink-3)', border: '1px solid var(--line-2)', borderRadius: 5, cursor: 'pointer' }}
                title="Ouvre l'espace client tel que le client le verra (lien 2h, usage interne)"
              >
                👁 Prévisualiser
              </button>
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>Valable 30 jours · révocable</div>
          </div>
          <div style={{ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>

            {/* Option email */}
            <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>
              <input type="checkbox" checked={sendEmail} onChange={e => setSendEmail(e.target.checked)}
                style={{ width: 14, height: 14, accentColor: 'var(--signal)', cursor: 'pointer' }} />
              <div>
                <div style={{ fontSize: 12, fontWeight: 500 }}>Envoyer par email au client</div>
                <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>Email envoyé à {d.email || 'l\'adresse du dossier'}</div>
              </div>
            </label>

            <button
              onClick={generate}
              disabled={generating}
              style={{
                padding: '9px 14px', fontSize: 12, fontWeight: 600,
                background: generating ? 'var(--paper-2)' : 'var(--signal)',
                color: generating ? 'var(--ink-4)' : '#0B1F12',
                border: 'none', borderRadius: 6, cursor: generating ? 'default' : 'pointer',
                transition: 'background 150ms',
              }}>
              {generating ? '…génération' : '🔗 Générer un lien'}
            </button>

            {/* Lien généré — affiché une seule fois */}
            {newLink && (
              <div style={{ background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 7, padding: '12px 14px' }}>
                <div style={{ fontSize: 10, color: 'var(--signal-deep)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 6 }}>
                  {newLink.emailSent ? '✓ Lien généré · email envoyé' : '✓ Lien généré'}
                </div>
                <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--ink-3)', wordBreak: 'break-all', marginBottom: 8, lineHeight: 1.5 }}>
                  {newLink.url}
                </div>
                <div style={{ display: 'flex', gap: 6 }}>
                  <button
                    onClick={() => copy(newLink.url)}
                    style={{ flex: 1, padding: '6px 10px', fontSize: 11, fontWeight: 600, background: copied ? 'var(--signal)' : 'var(--paper)', color: copied ? '#0B1F12' : 'var(--ink)', border: '1px solid var(--line-2)', borderRadius: 5, cursor: 'pointer', transition: 'all 150ms' }}>
                    {copied ? '✓ Copié !' : 'Copier le lien'}
                  </button>
                  <button onClick={() => window.open(newLink.url, '_blank')}
                    style={{ padding: '6px 10px', fontSize: 11, background: 'var(--paper)', color: 'var(--ink-3)', border: '1px solid var(--line-2)', borderRadius: 5, cursor: 'pointer' }}>
                    Ouvrir →
                  </button>
                </div>
                <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 8 }}>
                  Expire le {fmtDate(newLink.expiresAt)} · Ce lien n'est affiché qu'une seule fois.
                </div>
              </div>
            )}
          </div>
        </div>

        {/* Liens actifs */}
        {activeTokens.length > 0 && (
          <div style={{ border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
            <div style={{ padding: '10px 16px', borderBottom: '1px solid var(--line)', background: 'var(--paper-2)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <div style={{ fontSize: 12, fontWeight: 600 }}>Liens actifs</div>
              <span style={{ fontSize: 10, padding: '2px 7px', background: 'var(--signal-tint)', color: 'var(--signal-deep)', border: '1px solid var(--signal-soft)', borderRadius: 999, fontWeight: 600 }}>{activeTokens.length}</span>
            </div>
            <div style={{ background: 'var(--paper)' }}>
              {activeTokens.map((t, i) => (
                <div key={t.id} style={{ padding: '10px 16px', borderBottom: i < activeTokens.length - 1 ? '1px solid var(--hairline)' : 'none', display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, alignItems: 'center' }}>
                  <div>
                    <div style={{ fontSize: 12, fontWeight: 500, color: 'var(--ink-2)' }}>{t.label || 'Lien sans étiquette'}</div>
                    <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 2 }}>
                      Par {t.issuedBy || '—'} · expire {fmtDate(t.expiresAt)}
                      {t.lastUsedAt && <span style={{ color: 'var(--signal-deep)', fontWeight: 600 }}> · vu {fmtRelative(t.lastUsedAt)}</span>}
                    </div>
                  </div>
                  <button onClick={() => revoke(t.id)}
                    style={{ padding: '4px 8px', fontSize: 10, fontWeight: 600, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 4, cursor: 'pointer', whiteSpace: 'nowrap' }}>
                    Révoquer
                  </button>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Tokens expirés/révoqués */}
        {expiredTokens.length > 0 && (
          <details style={{ fontSize: 11 }}>
            <summary style={{ cursor: 'pointer', color: 'var(--ink-4)', padding: '4px 0', userSelect: 'none' }}>
              ▸ {expiredTokens.length} lien{expiredTokens.length > 1 ? 's' : ''} expiré{expiredTokens.length > 1 ? 's' : ''} ou révoqué{expiredTokens.length > 1 ? 's' : ''}
            </summary>
            <div style={{ marginTop: 8, border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden' }}>
              {expiredTokens.map((t, i) => (
                <div key={t.id} style={{ padding: '8px 12px', borderBottom: i < expiredTokens.length - 1 ? '1px solid var(--hairline)' : 'none', background: 'var(--paper-2)' }}>
                  <div style={{ color: 'var(--ink-4)' }}>{t.label || '—'}</div>
                  <div style={{ fontSize: 10, color: 'var(--ink-5)', marginTop: 1 }}>
                    {t.revokedAt ? `Révoqué ${fmtDate(t.revokedAt)}` : `Expiré ${fmtDate(t.expiresAt)}`}
                  </div>
                </div>
              ))}
            </div>
          </details>
        )}

        {/* Aperçu portail */}
        <div style={{ padding: '11px 14px', background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 8, fontSize: 11, color: 'var(--ink-3)', lineHeight: 1.5 }}>
          <strong style={{ color: 'var(--ink-2)', display: 'block', marginBottom: 3 }}>Aperçu de l'expérience client</strong>
          Le client accède à une page épurée avec uniquement les sections que vous avez activées. La section "Obligations" affiche ses échéances BACS / Tertiaire avec un compte à rebours OPERAT.
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { DossierDetail, DossierAppointments });
