/* global React */
// CRM — Modal création opportunité (Audits Énergies) + sync Odoo
// UI Claude Design IMMUTABLE : injection par modal overlay.

(function () {
  const { useState, useEffect } = React;

  const STAGES_CRM = [
    { v: 'prospect',          l: 'Prospect' },
    { v: 'etude',             l: 'Étude en cours' },
    { v: 'devis',             l: 'Devis envoyé' },
    { v: 'devis_signe',       l: 'Devis signé' },
    { v: 'audit_planifie',    l: 'Audit planifié' },
    { v: 'dossier_constitue', l: 'Dossier constitué' },
    { v: 'dossier_depose',    l: 'Déposé délégataire' },
    { v: 'prime_versee',      l: 'Prime versée' },
  ];

  function CreateOpportunityModal({ onClose, onCreated, prefillClient }) {
    const API = (window.AE_API && window.AE_API.BASE) || '';
    const [kind, setKind] = useState('quote');
    const [stage, setStage] = useState('prospect');
    const [picked, setPicked] = useState(prefillClient || null); // Règle 3.a : ClientPicker unifié
    const [clientNom, setClientNom] = useState(prefillClient?.lastName || '');
    const [clientPrenom, setClientPrenom] = useState(prefillClient?.firstName || '');
    const [clientEmail, setClientEmail] = useState(prefillClient?.email || '');
    const [clientTel, setClientTel] = useState(prefillClient?.phone || '');
    const [societe, setSociete] = useState(prefillClient?.isCompany ? prefillClient?.name : '');
    const [siret, setSiret] = useState(prefillClient?.siret || '');
    const [adresse, setAdresse] = useState(prefillClient?.street || '');
    const [montant, setMontant] = useState(0);
    const [notes, setNotes] = useState('');
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState(null);
    const [addrSuggestions, setAddrSuggestions] = useState([]);
    const [sireneResult, setSireneResult] = useState(null);

    // Propriétaire (Règle 5 + contrôle accès direction)
    const me = window.ME || {};
    const isDirection = ['pdg', 'direction'].includes(me.role);
    const myName = me.name || [me.firstName, me.lastName].filter(Boolean).join(' ');
    const [ownerName, setOwnerName] = useState(myName); // auto-assigné au créateur
    const [collabs, setCollabs] = useState([]);
    useEffect(() => {
      if (!isDirection) return;
      fetch(`${API}/api/collaborators?active=true`)
        .then(r => r.json())
        .then(d => setCollabs(d.collaborators || []))
        .catch(() => {});
    }, []); // eslint-disable-line

    // Lorsque le user pick un client via ClientPicker → auto-fill tous les champs liés
    const onPickClient = (c) => {
      setPicked(c);
      if (!c) return;
      if (c.isCompany) {
        setSociete(c.name || '');
      } else {
        const parts = (c.name || '').split(' ');
        setClientPrenom(c.firstName || parts[0] || '');
        setClientNom(c.lastName || parts.slice(1).join(' ') || '');
      }
      setClientEmail(c.email || '');
      setClientTel(c.phone || c.mobile || '');
      if (c.siret) setSiret(c.siret);
      if (c.street) setAdresse(c.street + (c.zip ? ', ' + c.zip : '') + (c.city ? ' ' + c.city : ''));
    };

    // BAN autocomplete (debounced)
    useEffect(() => {
      if (!adresse || adresse.length < 4) { setAddrSuggestions([]); return; }
      let cancelled = false;
      const t = setTimeout(() => {
        fetch(`${API}/api/integrations/ban?q=${encodeURIComponent(adresse)}`)
          .then(r => r.json())
          .then(res => { if (!cancelled && res.features) setAddrSuggestions(res.features.slice(0, 5)); })
          .catch(() => {});
      }, 300);
      return () => { cancelled = true; clearTimeout(t); };
    }, [adresse]);

    // SIRET lookup (14 digits → auto-fill société)
    useEffect(() => {
      const clean = (siret || '').replace(/\s+/g, '');
      if (clean.length !== 14) { setSireneResult(null); return; }
      fetch(`${API}/api/integrations/siret?siret=${clean}`)
        .then(r => r.json())
        .then(res => {
          setSireneResult(res);
          if (res && res.nom_complet && !societe) setSociete(res.nom_complet);
        })
        .catch(() => {});
    }, [siret]);

    const doCreate = async () => {
      setBusy(true); setError(null);
      try {
        // Règle 3.a : si picked existe, on réutilise contactId/organizationId au lieu de créer en double
        let orgId = picked?.organizationId || null;
        let contactId = picked?.contactId || null;
        let odooId = picked?.odooId || null;

        // 1. Upsert Organization (si picked est orga, on l'utilise ; sinon upsert depuis société saisie)
        if (!orgId && societe.trim()) {
          const org = await fetch(`${API}/api/organizations`, {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ name: societe.trim(), siret: (siret || '').replace(/\s+/g, '') || null, description: siret ? 'SIRET ' + siret : 'Créé depuis CRM', odooId }),
          }).then(r => r.json()).catch(() => null);
          orgId = org?.id || null;
        }
        // 2. Upsert Contact (si picked.contactId, on le garde ; sinon on upsert par email)
        if (!contactId && (clientNom || clientPrenom || clientEmail)) {
          const contact = await fetch(`${API}/api/contacts`, {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              firstName: clientPrenom || null, lastName: clientNom || null,
              email: clientEmail || null, phone: clientTel || null,
              siret: (siret || '').replace(/\s+/g, '') || null,
              organizationId: orgId, odooId,
              tags: ['crm', kind],
            }),
          }).then(r => r.json()).catch(() => null);
          contactId = contact?.id || null;
        }
        // 3. Create Opportunity (avec liens complets contactId + organizationId + externalId Odoo)
        const opp = await fetch(`${API}/api/crm/opportunities`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            name: societe || `${clientPrenom} ${clientNom}`.trim() || 'Nouvelle opportunité',
            kind, source: picked?.source === 'odoo' ? 'odoo' : 'manual', discoveryStage: stage, stage: 'proposal', state: 'draft',
            amount: montant,
            partnerName: societe || `${clientPrenom} ${clientNom}`.trim(),
            partnerEmail: clientEmail || null,
            contactId, organizationId: orgId,
            externalId: odooId ? `odoo:partner:${odooId}` : null,
            probability: 50,
            ownerName: ownerName || myName || null,
            description: notes + (adresse ? `\nAdresse : ${adresse}` : ''),
          }),
        }).then(r => r.json()).catch(() => null);
        if (!opp || opp.error) throw new Error(opp?.error || 'Échec création opportunité');
        // Règle 3.a : propager + hydrate caches globaux
        if (window.AE_CLIENT_PICKER?.propagate) window.AE_CLIENT_PICKER.propagate('opportunity', opp.id, picked);
        if (window.AE_API?.hydrate) window.AE_API.hydrate();
        onCreated && onCreated(opp);
        onClose();
      } catch (e) {
        setError(e.message);
      } finally {
        setBusy(false);
      }
    };

    const inputStyle = { padding: '7px 10px', fontSize: 13, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', color: 'var(--ink)', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box' };
    const labelStyle = { fontSize: 10, color: 'var(--ink-4)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.4 };
    const fieldStyle = { display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 };

    return (
      <div
        style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(14,16,16,0.6)', display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)', padding: 16 }}
        onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
      >
        <div style={{ width: 'min(700px, 100%)', maxHeight: '90vh', overflowY: 'auto', background: 'var(--paper)', borderRadius: 12, boxShadow: 'var(--shadow-3)', border: '1px solid var(--line-2)', boxSizing: 'border-box' }}>
          <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10, position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 1 }}>
            <Dot tone="signal" size={8} pulse />
            <div>
              <div style={{ fontSize: 14, fontWeight: 600 }}>Nouvelle opportunité</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>CRM Audits Énergies · création manuelle</div>
            </div>
            <span style={{ flex: 1 }} />
            <button onClick={onClose} style={{ color: 'var(--ink-4)' }}><Icon.cross /></button>
          </div>

          <div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 14 }}>
            {/* Type + stage */}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <label style={fieldStyle}>
                <span style={labelStyle}>Type d'opportunité</span>
                <select value={kind} onChange={e => setKind(e.target.value)} style={inputStyle}>
                  <option value="lead">Lead (piste commerciale)</option>
                  <option value="quote">Quote (devis qualifié)</option>
                </select>
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Stage initial</span>
                <select value={stage} onChange={e => setStage(e.target.value)} style={inputStyle}>
                  {STAGES_CRM.map(s => <option key={s.v} value={s.v}>{s.l}</option>)}
                </select>
              </label>
            </div>

            {/* Client — ClientPicker unifié (Règle 3.a) */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>
              Client <span style={{ color: 'var(--ink-5)', fontWeight: 500 }}>· 101 Odoo + {(window.AE_CLIENT_CACHE?.get()?.ae?.length) || '105'} AE contacts</span>
            </div>
            <label style={fieldStyle}>
              <span style={labelStyle}>Rechercher un client existant ou créer</span>
              {window.ClientPicker && <window.ClientPicker value={picked} onChange={onPickClient} placeholder="Taper nom / email / SIRET… (Odoo + AE)" autoFocus />}
            </label>

            {/* Contact */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>Contact {picked && <span style={{ color: 'var(--signal-deep)', fontWeight: 500 }}>· prérempli depuis {picked.source === 'odoo' ? 'Odoo' : picked.source === 'ae_org' ? 'Orga AE' : 'AE'}</span>}</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <label style={fieldStyle}><span style={labelStyle}>Nom</span><input value={clientNom} onChange={e => setClientNom(e.target.value)} placeholder="Dupont" style={inputStyle} /></label>
              <label style={fieldStyle}><span style={labelStyle}>Prénom</span><input value={clientPrenom} onChange={e => setClientPrenom(e.target.value)} placeholder="Jean" style={inputStyle} /></label>
              <label style={fieldStyle}><span style={labelStyle}>Email</span><input type="email" value={clientEmail} onChange={e => setClientEmail(e.target.value)} placeholder="client@exemple.fr" style={inputStyle} /></label>
              <label style={fieldStyle}><span style={labelStyle}>Téléphone</span><input value={clientTel} onChange={e => setClientTel(e.target.value)} placeholder="06 12 34 56 78" style={inputStyle} /></label>
            </div>

            {/* Société */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>
              Société <span style={{ color: 'var(--ink-5)', fontWeight: 500 }}>· recherche SIRENE INSEE auto</span>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <label style={fieldStyle}><span style={labelStyle}>SIRET (14 chiffres)</span><input value={siret} onChange={e => setSiret(e.target.value)} placeholder="Auto-remplit société" style={inputStyle} /></label>
              <label style={fieldStyle}><span style={labelStyle}>Raison sociale</span><input value={societe} onChange={e => setSociete(e.target.value)} placeholder="SARL Exemple" style={inputStyle} /></label>
            </div>
            {sireneResult && sireneResult.nom_complet && (
              <div style={{ padding: '6px 10px', fontSize: 10, color: 'var(--ink-3)', background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 5 }}>
                ✓ SIRENE : <strong>{sireneResult.nom_complet}</strong> · {sireneResult.activite_principale || ''} · {sireneResult.tranche_effectifs || ''}
              </div>
            )}

            {/* Adresse chantier + BAN */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>
              Adresse chantier <span style={{ color: 'var(--ink-5)', fontWeight: 500 }}>· autocomplete BAN</span>
            </div>
            <label style={{ ...fieldStyle, position: 'relative' }}>
              <input value={adresse} onChange={e => setAdresse(e.target.value)} placeholder="12 rue... 75001 Paris" style={inputStyle} />
              {addrSuggestions.length > 0 && (
                <div style={{ position: 'absolute', top: '100%', left: 0, right: 0, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5, boxShadow: 'var(--shadow-2)', zIndex: 10, marginTop: 2 }}>
                  {addrSuggestions.map((s, i) => (
                    <button key={i} type="button" onClick={() => { setAdresse(s.properties?.label || ''); setAddrSuggestions([]); }} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '6px 10px', fontSize: 12, borderBottom: i < addrSuggestions.length - 1 ? '1px solid var(--hairline)' : 'none', color: 'var(--ink-2)' }}>
                      {s.properties?.label || '—'}
                    </button>
                  ))}
                </div>
              )}
            </label>

            {/* Propriétaire — dropdown si direction, sinon verrouillé sur soi-même */}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              {isDirection ? (
                <label style={fieldStyle}>
                  <span style={labelStyle}>Propriétaire (collaborateur)</span>
                  <select value={ownerName} onChange={e => setOwnerName(e.target.value)} style={inputStyle}>
                    <option value="">— Sélectionner —</option>
                    {(collabs.length ? collabs : (myName ? [{ id: 'me', firstName: me.firstName || myName, lastName: me.lastName || '', title: me.title || me.role }] : [])).map((c, i) => {
                      const full = [c.firstName, c.lastName].filter(Boolean).join(' ');
                      const hint = c.title || c.role || '';
                      return <option key={c.id || i} value={full}>{full}{hint ? ` · ${hint}` : ''}</option>;
                    })}
                  </select>
                </label>
              ) : (
                <label style={fieldStyle}>
                  <span style={{ ...labelStyle, display: 'flex', alignItems: 'center', gap: 4 }}>
                    Propriétaire
                    <svg width="9" height="9" viewBox="0 0 16 16" fill="var(--ink-4)"><path d="M12 7V5a4 4 0 0 0-8 0v2H3a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1h-1zm-6-2a2 2 0 1 1 4 0v2H6V5z"/></svg>
                  </span>
                  <div style={{ ...inputStyle, background: 'var(--paper)', cursor: 'not-allowed', color: 'var(--ink-2)', display: 'flex', alignItems: 'center', gap: 6, userSelect: 'none' }}>
                    <span style={{ flex: 1 }}>{myName || '—'}</span>
                    <span style={{ fontSize: 9, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>auto</span>
                  </div>
                </label>
              )}
              <label style={fieldStyle}><span style={labelStyle}>Montant estimé (€)</span><input type="number" value={montant} onChange={e => setMontant(Number(e.target.value) || 0)} style={inputStyle} /></label>
            </div>

            {/* Notes */}
            <label style={fieldStyle}><span style={labelStyle}>Notes</span><input value={notes} onChange={e => setNotes(e.target.value)} placeholder="Contexte, mots-clés…" style={inputStyle} /></label>

            {error && <div style={{ fontSize: 11, color: 'var(--rouge)' }}>Erreur : {error}</div>}

            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 6 }}>
              <button onClick={onClose} style={{ padding: '8px 14px', fontSize: 12, color: 'var(--ink-3)' }}>Annuler</button>
              <button onClick={doCreate} disabled={busy || (!clientNom && !societe)} style={{ padding: '8px 14px', fontSize: 12, fontWeight: 600, background: (!clientNom && !societe) ? 'var(--paper-3)' : 'var(--signal)', color: (!clientNom && !societe) ? 'var(--ink-4)' : 'var(--ink)', border: 'none', borderRadius: 5 }}>
                {busy ? 'Création…' : 'Créer l\'opportunité'}
              </button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  // Bouton "Sync Odoo" — déclenche POST /api/crm/sync/run
  async function triggerOdooSync() {
    const API = (window.AE_API && window.AE_API.BASE) || '';
    const res = await fetch(`${API}/api/crm/sync/run`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ adapter: 'odoo', mode: 'incremental' }),
    }).then(r => r.json()).catch(e => ({ ok: false, error: e.message }));
    return res;
  }

  // ────────────────────────────────────────────────────────────
  // OpportunityDetailModal — édition complète d'une opportunité
  // Règle n°5 CRUD : read + update + delete + transition stage
  // ────────────────────────────────────────────────────────────
  function OpportunityDetailModal({ dossier, onClose, onUpdated, onDeleted }) {
    const API = (window.AE_API && window.AE_API.BASE) || '';
    const [full, setFull] = useState(null);
    const [loading, setLoading] = useState(true);
    const [editMode, setEditMode] = useState(false);
    const [draft, setDraft] = useState({});
    const [saveState, setSaveState] = useState({ loading: false, message: null, error: null });
    const [collabs, setCollabs] = useState([]); // liste pour le picker direction
    const [previewLoading, setPreviewLoading] = useState(false);

    // Identité session courante + permission
    const me = window.ME || {};
    const isDirection = ['pdg', 'direction'].includes(me.role);

    // Charger la liste des collaborateurs (direction uniquement — pour le picker propriétaire)
    useEffect(() => {
      if (!isDirection) return;
      fetch(`${API}/api/collaborators?active=true`)
        .then(r => r.json())
        .then(d => setCollabs(d.collaborators || []))
        .catch(() => {});
    }, []); // eslint-disable-line

    // dossier.ref peut être "S00001" (reference Odoo) ou "DOS-2026-XXX".
    // On cherche l'opp backend via sa référence :
    useEffect(() => {
      if (!dossier) return;
      fetch(`${API}/api/crm/opportunities?search=${encodeURIComponent(dossier.ref || '')}&limit=5`)
        .then(r => r.json())
        .then(d => {
          const hit = (d?.opportunities || []).find(o => o.reference === dossier.ref || o.name === dossier.ref) || (d?.opportunities || [])[0];
          if (hit) {
            setFull(hit);
            setDraft({
              name: hit.name || '', partnerName: hit.partnerName || '', partnerEmail: hit.partnerEmail || '',
              amount: hit.amount || 0, probability: hit.probability || 50,
              description: hit.description || '', ownerName: hit.ownerName || '', expectedClosing: hit.expectedClosing || '',
            });
          }
        })
        .catch(() => {})
        .finally(() => setLoading(false));
    }, [dossier?.ref]);

    const saveEdit = async () => {
      if (!full) return;
      setSaveState({ loading: true, message: null, error: null });
      try {
        const r = await fetch(`${API}/api/crm/opportunities/${full.id}`, {
          method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(draft),
        }).then(r => r.json());
        if (!r?.ok) throw new Error(r?.error || 'Échec');
        setFull(r.opportunity);
        setEditMode(false);
        setSaveState({ loading: false, message: '✓ Enregistré', error: null });
        if (onUpdated) onUpdated(r.opportunity);
        if (window.AE_API?.hydrate) window.AE_API.hydrate();
        setTimeout(() => setSaveState({ loading: false, message: null, error: null }), 3000);
      } catch (e) { setSaveState({ loading: false, message: null, error: e.message }); }
    };

    const deleteOpp = async () => {
      if (!full) return;
      if (!window.confirm(`Supprimer définitivement l'opportunité « ${full.name} » ?`)) return;
      await fetch(`${API}/api/crm/opportunities/${full.id}`, { method: 'DELETE' }).catch(() => {});
      if (onDeleted) onDeleted(full.id);
      if (window.AE_API?.hydrate) window.AE_API.hydrate();
      onClose();
    };

    const duplicateOpp = async () => {
      if (!full) return;
      if (!window.confirm(`Dupliquer l'opportunité « ${full.name} » ?\n\nLa copie sera créée en stage "prospect" avec les mêmes données client.`)) return;
      setSaveState({ loading: true, message: null, error: null });
      try {
        const r = await fetch(`${API}/api/crm/opportunities/${full.id}/duplicate`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({}),
        }).then(x => x.json());
        if (!r?.ok) throw new Error(r?.error || 'Échec duplication');
        setSaveState({ loading: false, message: `✓ Dupliquée : opp #${r.opportunity.id}`, error: null });
        if (window.AE_API?.hydrate) window.AE_API.hydrate();
        if (onUpdated) onUpdated(r.opportunity);
        setTimeout(() => setSaveState({ loading: false, message: null, error: null }), 4000);
      } catch (e) { setSaveState({ loading: false, message: null, error: e.message }); }
    };

    const previewPortal = async () => {
      const dossierId = full?.ceeDossierId;
      if (!dossierId) {
        setSaveState({ loading: false, message: null, error: 'Aucun dossier CEE lié à cette opportunité. Créez d\'abord un dossier depuis l\'onglet Dossiers.' });
        setTimeout(() => setSaveState(s => ({ ...s, error: null })), 4000);
        return;
      }
      setPreviewLoading(true);
      try {
        const r = await fetch(`${API}/api/client-portal/invite`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ dossierId, preview: true }),
        }).then(x => x.json());
        if (!r?.ok) throw new Error(r?.error || 'Échec génération');
        window.open(r.portalUrl, '_blank');
      } catch (e) {
        setSaveState({ loading: false, message: null, error: `Vue client : ${e.message}` });
        setTimeout(() => setSaveState(s => ({ ...s, error: null })), 4000);
      } finally { setPreviewLoading(false); }
    };

    const transitionStage = async (newStage) => {
      if (!full) return;
      setSaveState({ loading: true, message: null, error: null });
      try {
        const r = await fetch(`${API}/api/crm/opportunities/${full.id}/stage`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ stage: newStage, by: 'UI', note: `Transition depuis ${full.discoveryStage} → ${newStage}` }),
        }).then(r => r.json());
        if (!r?.ok) throw new Error(r?.error || 'Échec transition');
        // Re-fetch opp
        const fresh = await fetch(`${API}/api/crm/opportunities/${full.id}`).then(r => r.json());
        if (fresh?.opportunity) setFull(fresh.opportunity);
        setSaveState({ loading: false, message: `✓ Stage → ${newStage}`, error: null });
        if (window.AE_API?.hydrate) window.AE_API.hydrate();
        setTimeout(() => setSaveState({ loading: false, message: null, error: null }), 3000);
      } catch (e) { setSaveState({ loading: false, message: null, error: e.message }); }
    };

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

    const STAGES = (window.STAGES || []).filter(s => s.key !== 'perdu');

    return (
      <div style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(14,16,16,0.6)', display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)', padding: 16 }}
        onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
        <div style={{ width: 'min(720px, 100%)', maxHeight: '92vh', overflowY: 'auto', 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, position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 1 }}>
            <Dot tone={dossier?.stage === 'prospect' ? 'muted' : dossier?.stage === 'prime_versee' ? 'signal' : 'amber'} size={8} pulse />
            <div>
              <div style={{ fontSize: 14, fontWeight: 600 }}>Opportunité · {dossier?.ref}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>{full?.source === 'odoo' ? `Odoo #${full?.externalId || full?.id}` : `Discovery #${full?.id || '—'}`} · {full?.discoveryStage || dossier?.stage || '—'}</div>
            </div>
            <span style={{ flex: 1 }} />
            {!editMode && full && (
              <>
                <button onClick={() => {
                  // Si collaborateur non-direction, pre-remplir avec son propre nom
                  if (!isDirection) {
                    const myName = me.name || [me.firstName, me.lastName].filter(Boolean).join(' ');
                    if (myName) setDraft(d => ({ ...d, ownerName: d.ownerName || myName }));
                  }
                  setEditMode(true);
                }} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 4 }}>✏️ Éditer</button>
                <button onClick={duplicateOpp} disabled={saveState.loading} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 4 }}>📋 Dupliquer</button>
                <button
                  onClick={previewPortal}
                  disabled={previewLoading}
                  title={full?.ceeDossierId ? `Prévisualiser le portail client (dossier #${full.ceeDossierId})` : 'Aucun dossier CEE lié'}
                  style={{ padding: '5px 10px', fontSize: 11, background: full?.ceeDossierId ? 'var(--signal-tint)' : 'var(--paper-2)', border: `1px solid ${full?.ceeDossierId ? 'var(--signal-soft)' : 'var(--line-2)'}`, borderRadius: 4, color: full?.ceeDossierId ? 'var(--signal-deep)' : 'var(--ink-4)', fontWeight: full?.ceeDossierId ? 600 : 400, cursor: full?.ceeDossierId ? 'pointer' : 'default' }}>
                  {previewLoading ? '…' : '🌐 Vue client'}
                </button>
                <button onClick={deleteOpp} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 4 }}>🗑 Supprimer</button>
              </>
            )}
            {editMode && (
              <>
                <button onClick={() => setEditMode(false)} disabled={saveState.loading} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 4 }}>Annuler</button>
                <button onClick={saveEdit} disabled={saveState.loading} style={{ padding: '5px 12px', fontSize: 11, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 4, fontWeight: 600 }}>
                  {saveState.loading ? '…' : '✓ Enregistrer'}
                </button>
              </>
            )}
            <button onClick={onClose} style={{ color: 'var(--ink-4)' }}><Icon.cross /></button>
          </div>

          <div style={{ padding: 20 }}>
            {loading && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>}
            {!loading && !full && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Opportunité introuvable en base.</div>}
            {saveState.message && <div style={{ marginBottom: 10, padding: '6px 10px', fontSize: 11, background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 5, color: 'var(--signal-deep)' }}>{saveState.message}</div>}
            {saveState.error && <div style={{ marginBottom: 10, padding: '6px 10px', fontSize: 11, background: 'var(--rouge-tint)', border: '1px solid var(--rouge)', borderRadius: 5, color: 'var(--rouge)' }}>⚠ {saveState.error}</div>}

            {full && (
              <>
                {/* Quick stage transitions */}
                <div style={{ marginBottom: 16 }}>
                  <div style={{ ...labelStyle, marginBottom: 6 }}>Transition stage ·  {full.discoveryStage}</div>
                  <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                    {STAGES.map(s => (
                      <button key={s.key} onClick={() => transitionStage(s.key)} disabled={saveState.loading || full.discoveryStage === s.key}
                        style={{
                          padding: '4px 10px', fontSize: 11,
                          background: full.discoveryStage === s.key ? 'var(--signal-tint)' : 'var(--paper-2)',
                          border: full.discoveryStage === s.key ? '1px solid var(--signal)' : '1px solid var(--line)',
                          borderRadius: 4,
                          color: full.discoveryStage === s.key ? 'var(--signal-deep)' : 'var(--ink-2)',
                          fontWeight: full.discoveryStage === s.key ? 600 : 500,
                        }}>{s.label}</button>
                    ))}
                  </div>
                </div>

                {!editMode ? (
                  <>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
                      <Kv2 label="Nom" v={full.name} />
                      <Kv2 label="Partenaire" v={full.partnerName} />
                      <Kv2 label="Email" v={full.partnerEmail} />
                      <Kv2 label="Montant" v={full.amount ? `${full.amount} €` : '—'} />
                      <Kv2 label="Probabilité" v={full.probability ? `${full.probability} %` : '—'} />
                      <Kv2 label="Propriétaire" v={full.ownerName} />
                      <Kv2 label="Date ouverture" v={full.dateOpen ? new Date(full.dateOpen).toLocaleDateString('fr-FR') : '—'} />
                      <Kv2 label="Dossier CEE" v={full.ceeDossierId ? `#${full.ceeDossierId}` : '—'} />
                      <div style={{ gridColumn: '1 / -1' }}><Kv2 label="Description" v={full.description} /></div>
                    </div>
                    {/* RDV liés (règle n°3 Interconnexion) */}
                    <OppAppointmentsPanel opp={full} />
                    {/* Chatter natif : pièces jointes + tâches planifiées + notes sur l'opp */}
                    {window.ChatterPanel && full.id && (
                      <window.ChatterPanel entityType="opportunity" entityId={full.id} />
                    )}
                    {/* Discussion interne — fil de commentaires sur cette opportunité */}
                    {window.EntityCommentsPanel && (
                      <div style={{ marginTop: 16 }}>
                        <window.EntityCommentsPanel targetKind="opportunite" targetId={full.id} compact />
                      </div>
                    )}
                  </>
                ) : (
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                    <EditF2 label="Nom" v={draft.name} onChange={v => setDraft(d => ({ ...d, name: v }))} full />
                    <EditF2 label="Partenaire" v={draft.partnerName} onChange={v => setDraft(d => ({ ...d, partnerName: v }))} />
                    <EditF2 label="Email partenaire" v={draft.partnerEmail} onChange={v => setDraft(d => ({ ...d, partnerEmail: v }))} type="email" />
                    <EditF2 label="Montant (€)" v={draft.amount} onChange={v => setDraft(d => ({ ...d, amount: Number(v) }))} type="number" />
                    <EditF2 label="Probabilité (%)" v={draft.probability} onChange={v => setDraft(d => ({ ...d, probability: Number(v) }))} type="number" />
                    <OwnerPickerField />
                    <EditF2 label="Date prévue closing" v={draft.expectedClosing} onChange={v => setDraft(d => ({ ...d, expectedClosing: v }))} type="date" />
                    <EditF2 label="Description" v={draft.description} onChange={v => setDraft(d => ({ ...d, description: v }))} full textarea />
                  </div>
                )}
              </>
            )}
          </div>
        </div>
      </div>
    );

    function Kv2({ label, v }) {
      return (
        <div>
          <div style={labelStyle}>{label}</div>
          <div style={{ fontSize: 12, color: v ? 'var(--ink)' : 'var(--ink-4)', fontWeight: v ? 500 : 400, whiteSpace: 'pre-wrap' }}>{v || '—'}</div>
        </div>
      );
    }
    function EditF2({ label, v, onChange, type, textarea, full }) {
      return (
        <div style={{ gridColumn: full ? '1 / -1' : 'auto', display: 'flex', flexDirection: 'column', gap: 3, minWidth: 0 }}>
          <span style={labelStyle}>{label}</span>
          {textarea
            ? <textarea value={v || ''} onChange={e => onChange(e.target.value)} rows={3} style={{ ...inputStyle, resize: 'vertical' }} />
            : <input type={type || 'text'} value={v ?? ''} onChange={e => onChange(e.target.value)} style={inputStyle} />}
        </div>
      );
    }

    // ── Champ Propriétaire avec contrôle d'accès ──────────────────────────────
    // Direction (pdg/direction) : dropdown de tous les collaborateurs actifs.
    // Autres rôles : nom pré-rempli avec le leur, champ verrouillé (🔒).
    // Seule la direction peut re-assigner à quelqu'un d'autre.
    function OwnerPickerField() {
      if (isDirection) {
        // Construire la liste : collaborateurs chargés OU au moins le me courant
        const opts = collabs.length
          ? collabs
          : (me.name ? [{ id: 'me', firstName: me.firstName || me.name, lastName: me.lastName || '', title: me.title || me.role }] : []);
        return (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 3, minWidth: 0 }}>
            <span style={labelStyle}>Propriétaire</span>
            <select
              value={draft.ownerName || ''}
              onChange={e => setDraft(d => ({ ...d, ownerName: e.target.value }))}
              style={{ ...inputStyle, appearance: 'auto', cursor: 'pointer' }}
            >
              <option value="">— Sélectionner un collaborateur —</option>
              {opts.map((c, i) => {
                const fullName = [c.firstName, c.lastName].filter(Boolean).join(' ');
                const hint = c.title || c.role || '';
                return (
                  <option key={c.id || i} value={fullName}>
                    {fullName}{hint ? ` · ${hint}` : ''}
                  </option>
                );
              })}
            </select>
            {!collabs.length && (
              <span style={{ fontSize: 9, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>
                Liste collaborateurs non chargée — vérifiez /api/collaborators
              </span>
            )}
          </div>
        );
      }

      // Collaborateur standard : verrouillé sur son propre nom
      const myName = me.name || [me.firstName, me.lastName].filter(Boolean).join(' ') || draft.ownerName || '—';
      return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 3, minWidth: 0 }}>
          <div style={{ ...labelStyle, display: 'flex', alignItems: 'center', gap: 4 }}>
            Propriétaire
            <svg width="9" height="9" viewBox="0 0 16 16" fill="var(--ink-4)">
              <path d="M12 7V5a4 4 0 0 0-8 0v2H3a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1h-1zm-6-2a2 2 0 1 1 4 0v2H6V5z"/>
            </svg>
          </div>
          <div style={{
            ...inputStyle,
            display: 'flex', alignItems: 'center', gap: 8,
            background: 'var(--paper)', cursor: 'not-allowed',
            color: 'var(--ink-2)', userSelect: 'none',
          }}>
            <span style={{ flex: 1 }}>{myName}</span>
            <span style={{ fontSize: 9, color: 'var(--ink-4)', whiteSpace: 'nowrap', fontFamily: 'var(--font-mono)' }}>
              Direction uniquement
            </span>
          </div>
        </div>
      );
    }
  }

  // ─── Panneau RDV liés à une opportunité (règle n°3 Interconnexion) ─────────
  function OppAppointmentsPanel({ opp }) {
    const API = (window.AE_API && window.AE_API.BASE) || '';
    const [appts, setAppts] = useState([]);
    const [loading, setLoading] = useState(false);

    const reload = async () => {
      if (!opp?.id) return;
      setLoading(true);
      try {
        const r = await fetch(`${API}/api/calendar/appointments`).then(x => x.json());
        if (r.ok) setAppts((r.appointments || []).filter(a => a.opportunityId === opp.id));
      } catch (_) {}
      setLoading(false);
    };
    useEffect(() => { reload(); }, [opp?.id]);

    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 () => {
      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: `RDV — ${opp.partnerName || opp.name}`,
            kind: 'rdv_commercial',
            scheduledAt: dt.toISOString(),
            durationMin: 60,
            opportunityId: opp.id,
            contactId: opp.contactId || null,
            clientName: opp.partnerName || null,
          }),
        }).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={{ marginTop: 16, border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: '12px 14px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
          <span style={{ fontSize: 13, fontWeight: 600 }}>📅 RDV liés</span>
          <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
            {appts.length} RDV · {upcoming.length} à venir
          </span>
          <span style={{ flex: 1 }} />
          <button onClick={reload} style={{ fontSize: 10, color: 'var(--ink-4)', padding: '2px 6px' }}>{loading ? '…' : '⟳'}</button>
          <button onClick={createNew}
            style={{ padding: '4px 10px', fontSize: 11, border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper)', color: 'var(--ink-2)', fontWeight: 500 }}>
            + Nouveau RDV
          </button>
        </div>

        {appts.length === 0 ? (
          <div style={{ padding: '14px 0', textAlign: 'center', fontSize: 11, color: 'var(--ink-4)' }}>
            Aucun RDV lié — créez-en un ou passez le stage à <code>audit_planifie</code> pour création auto.
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {upcoming.map(a => (
              <button key={a.id} onClick={() => openInCalendar(a)}
                style={{
                  display: 'grid', gridTemplateColumns: '20px 1fr auto auto', gap: 10, alignItems: 'center',
                  padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, background: 'var(--paper)',
                  textAlign: 'left', cursor: 'pointer', fontSize: 11,
                }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: a.color || 'var(--signal)' }} />
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 12, 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}` : ''}
                  </div>
                </div>
                <span className="mono" style={{ fontSize: 10, 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: 10 }}>→</span>
              </button>
            ))}
            {past.length > 0 && (
              <details style={{ marginTop: 6 }}>
                <summary style={{ fontSize: 10, color: 'var(--ink-4)', cursor: 'pointer', padding: '4px 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: '5px 8px', border: '1px solid var(--hairline)', borderRadius: 5, background: 'transparent', textAlign: 'left', fontSize: 10, color: 'var(--ink-4)' }}>
                      <span>{a.title}</span>
                      <span className="mono">{new Date(a.scheduledAt).toLocaleDateString('fr-FR')} · {a.status}</span>
                    </button>
                  ))}
                </div>
              </details>
            )}
          </div>
        )}
      </div>
    );
  }

  // ─── Panneau RDV liés à un Contact (règle n°3 Interconnexion) ──────────────
  // mode = 'odoo' → filtre par client.id == odooPartnerId via Contact résolu
  // mode = 'ae'   → filtre direct par contactId == client.id
  function ContactAppointmentsPanel({ client, mode }) {
    const API = (window.AE_API && window.AE_API.BASE) || '';
    const [appts, setAppts] = useState([]);
    const [loading, setLoading] = useState(false);
    const [resolvedId, setResolvedId] = useState(null);

    const reload = async () => {
      if (!client?.id) return;
      setLoading(true);
      try {
        // Si Odoo : on doit retrouver le Contact AE par odooPartnerId
        let cid = client.id;
        if (mode === 'odoo') {
          const cs = await fetch(`${API}/api/contacts`).then(x => x.json()).catch(() => []);
          const match = (Array.isArray(cs) ? cs : []).find(c => c.odooPartnerId === client.id);
          cid = match ? match.id : null;
        }
        setResolvedId(cid);
        if (!cid) { setAppts([]); setLoading(false); return; }
        const r = await fetch(`${API}/api/calendar/appointments`).then(x => x.json());
        if (r.ok) setAppts((r.appointments || []).filter(a => a.contactId === cid));
      } catch (_) {}
      setLoading(false);
    };
    useEffect(() => { reload(); }, [client?.id, mode]);

    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 (!resolvedId) {
        if (mode === 'odoo') return alert('Ce client Odoo n\'est pas encore lié à un Contact AE. Importez-le d\'abord via /api/contacts.');
        return alert('Contact non identifié');
      }
      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');
      const clientName = client.name || [client.firstName, client.lastName].filter(Boolean).join(' ');
      try {
        const r = await fetch(`${API}/api/calendar/appointments`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            title: `RDV — ${clientName}`,
            kind: 'rdv_commercial',
            scheduledAt: dt.toISOString(),
            durationMin: 60,
            contactId: resolvedId,
            organizationId: client.organizationId || null,
            clientName,
          }),
        }).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={{ marginTop: 16, border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: '12px 14px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
          <span style={{ fontSize: 13, fontWeight: 600 }}>📅 RDV liés</span>
          <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
            {appts.length} RDV · {upcoming.length} à venir
          </span>
          <span style={{ flex: 1 }} />
          <button onClick={reload} style={{ fontSize: 10, color: 'var(--ink-4)', padding: '2px 6px' }}>{loading ? '…' : '⟳'}</button>
          <button onClick={createNew}
            style={{ padding: '4px 10px', fontSize: 11, border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper)', color: 'var(--ink-2)', fontWeight: 500 }}>
            + Nouveau RDV
          </button>
        </div>

        {appts.length === 0 ? (
          <div style={{ padding: '14px 0', textAlign: 'center', fontSize: 11, color: 'var(--ink-4)' }}>
            {mode === 'odoo' && !resolvedId
              ? 'Client Odoo non encore lié à un Contact AE — créez un Contact pour activer les RDV.'
              : 'Aucun RDV avec ce client. Cliquez "+ Nouveau RDV" ou créez une opportunité avec ce contact.'}
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {upcoming.map(a => (
              <button key={a.id} onClick={() => openInCalendar(a)}
                style={{
                  display: 'grid', gridTemplateColumns: '20px 1fr auto auto', gap: 10, alignItems: 'center',
                  padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, background: 'var(--paper)',
                  textAlign: 'left', cursor: 'pointer', fontSize: 11,
                }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: a.color || 'var(--signal)' }} />
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 12, 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.opportunityId ? ` · opp #${a.opportunityId}` : ''}
                  </div>
                </div>
                <span className="mono" style={{ fontSize: 10, 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: 10 }}>→</span>
              </button>
            ))}
            {past.length > 0 && (
              <details style={{ marginTop: 6 }}>
                <summary style={{ fontSize: 10, color: 'var(--ink-4)', cursor: 'pointer', padding: '4px 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: '5px 8px', border: '1px solid var(--hairline)', borderRadius: 5, background: 'transparent', textAlign: 'left', fontSize: 10, color: 'var(--ink-4)' }}>
                      <span>{a.title}</span>
                      <span className="mono">{new Date(a.scheduledAt).toLocaleDateString('fr-FR')} · {a.status}</span>
                    </button>
                  ))}
                </div>
              </details>
            )}
          </div>
        )}
      </div>
    );
  }

  window.CreateOpportunityModal = CreateOpportunityModal;
  window.OpportunityDetailModal = OpportunityDetailModal;
  window.OppAppointmentsPanel = OppAppointmentsPanel;
  window.ContactAppointmentsPanel = ContactAppointmentsPanel;
  window.triggerOdooSync = triggerOdooSync;
})();
