/* global React */
// Feature modals : Produits, Organisations, Devis, Communication, Audits.
// UI Claude Design IMMUTABLE : tous accessibles via CommandPalette ⌘K.

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

  const API = () => (window.AE_API && window.AE_API.BASE) || '';

  // ────────────────────────────────────────────────────────────
  // ShellModal — wrapper commun respectant la charte Claude Design
  // ────────────────────────────────────────────────────────────
  function ShellModal({ title, subtitle, tone, onClose, children, footer, width }) {
    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(${width || 780}px, 100%)`, maxHeight: '92vh', display: 'flex', flexDirection: 'column', background: 'var(--paper)', borderRadius: 12, boxShadow: 'var(--shadow-3)', border: '1px solid var(--line-2)', boxSizing: 'border-box', overflow: 'hidden' }}>
          <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
            <Dot tone={tone || 'signal'} size={8} pulse />
            <div>
              <div style={{ fontSize: 14, fontWeight: 600 }}>{title}</div>
              {subtitle && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>{subtitle}</div>}
            </div>
            <span style={{ flex: 1 }} />
            <button onClick={onClose} style={{ color: 'var(--ink-4)' }}><Icon.cross /></button>
          </div>
          <div style={{ flex: 1, overflowY: 'auto', padding: 20 }}>{children}</div>
          {footer && <div style={{ padding: '10px 20px', borderTop: '1px solid var(--line)', display: 'flex', gap: 8, justifyContent: 'flex-end' }}>{footer}</div>}
        </div>
      </div>
    );
  }

  // ────────────────────────────────────────────────────────────
  // ProductsModal — catalogue ProductCatalog (EPREL + Odoo + seeds + manual)
  // ────────────────────────────────────────────────────────────
  function ProductsModal({ onClose }) {
    const [q, setQ] = useState('');
    const [brand, setBrand] = useState('');
    const [category, setCategory] = useState('');
    const [results, setResults] = useState([]);
    const [brands, setBrands] = useState([]);
    const [categories, setCategories] = useState([]);
    const [loading, setLoading] = useState(false);
    // Règle n°5 CRUD — édition / création / suppression manuelle
    const [selected, setSelected] = useState(null);
    const [editMode, setEditMode] = useState(false);
    const [draft, setDraft] = useState(null);
    const [showNew, setShowNew] = useState(false);
    const [saveState, setSaveState] = useState({ loading: false, message: null, error: null });

    const reload = () => {
      setLoading(true);
      const params = new URLSearchParams();
      if (q) params.set('q', q);
      if (brand) params.set('brand', brand);
      if (category) params.set('category', category);
      params.set('limit', '50');
      return fetch(`${API()}/api/products/search?${params}`)
        .then(r => r.json())
        .then(d => setResults(Array.isArray(d) ? d : d?.products || []))
        .catch(() => setResults([]))
        .finally(() => setLoading(false));
    };

    useEffect(() => {
      fetch(`${API()}/api/products/brands`).then(r => r.json()).then(d => setBrands(d?.brands || [])).catch(() => {});
      fetch(`${API()}/api/products/categories`).then(r => r.json()).then(d => setCategories(d?.categories || [])).catch(() => {});
    }, []);

    useEffect(() => {
      const t = setTimeout(reload, 250);
      return () => clearTimeout(t);
    }, [q, brand, category]);

    const startEdit = (p) => {
      setSelected(p);
      setDraft({
        id: p.id,
        source: p.source,
        brand: p.brand || '',
        model: p.model || '',
        category: p.category || '',
        sub_category: p.sub_category || '',
        description: p.description || '',
        price_ht: p.price_ht || '',
        energy_class: p.energy_class || '',
        image_url: p.image_url || '',
        product_url: p.product_url || '',
        specs: p.specs || {},
        fostCodes: (p.specs && Array.isArray(p.specs.fostCodes)) ? p.specs.fostCodes.join(', ') : '',
      });
      setEditMode(true);
      setSaveState({ loading: false, message: null, error: null });
    };

    const cancelEdit = () => { setEditMode(false); setDraft(null); setSelected(null); };

    const saveEdit = async () => {
      if (!draft) return;
      setSaveState({ loading: true, message: null, error: null });
      try {
        const fostArr = (draft.fostCodes || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean);
        const specs = { ...(draft.specs || {}), fostCodes: fostArr };
        const body = {
          brand: draft.brand, model: draft.model,
          category: draft.category, sub_category: draft.sub_category,
          description: draft.description,
          specs,
          price_ht: draft.price_ht === '' ? null : parseFloat(draft.price_ht),
          energy_class: draft.energy_class || null,
          image_url: draft.image_url || null,
          product_url: draft.product_url || null,
        };
        const r = await fetch(`${API()}/api/products/${draft.id}`, {
          method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
        }).then(r => r.json());
        if (r?.status !== 'ok') throw new Error(r?.message || 'Erreur enregistrement');
        setSaveState({ loading: false, message: '✓ Enregistré', error: null });
        await reload();
        setTimeout(() => cancelEdit(), 900);
      } catch (e) { setSaveState({ loading: false, message: null, error: e.message }); }
    };

    const deleteProduct = async (p) => {
      const isManual = p.source === 'manual';
      const msg = isManual
        ? `Supprimer définitivement « ${p.brand} — ${p.model} » ?`
        : `Désactiver « ${p.brand} — ${p.model} » (source ${p.source}, hard delete refusé) ?`;
      if (!window.confirm(msg)) return;
      const r = await fetch(`${API()}/api/products/${p.id}`, { method: 'DELETE' }).then(r => r.json()).catch(() => null);
      if (r?.status === 'ok') { await reload(); if (selected?.id === p.id) cancelEdit(); }
      else window.alert(r?.message || 'Erreur suppression');
    };

    const createProduct = async (form) => {
      setSaveState({ loading: true, message: null, error: null });
      try {
        const fostArr = (form.fostCodes || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean);
        const specs = { ...(form.specs || {}) };
        if (fostArr.length) specs.fostCodes = fostArr;
        const body = {
          brand: form.brand, model: form.model,
          category: form.category || 'autre', sub_category: form.sub_category,
          description: form.description, specs,
          price_ht: form.price_ht === '' ? null : parseFloat(form.price_ht),
          energy_class: form.energy_class || null,
        };
        const r = await fetch(`${API()}/api/products`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
        }).then(r => r.json());
        if (r?.status !== 'ok') throw new Error(r?.message || 'Erreur création');
        setSaveState({ loading: false, message: '✓ Produit créé', error: null });
        await reload();
        setTimeout(() => { setShowNew(false); setSaveState({ loading: false, message: null, error: null }); }, 900);
      } catch (e) { setSaveState({ loading: false, message: null, error: e.message }); }
    };

    const inputStyle = { padding: '7px 10px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', color: 'var(--ink)', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box' };

    return (
      <ShellModal title="Catalogue produits" subtitle="EPREL + Odoo + seeds · recherche live + CRUD manuel" tone="signal" onClose={onClose} width={1000}>
        <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr auto', gap: 10, marginBottom: 14 }}>
          <input value={q} onChange={e => setQ(e.target.value)} placeholder="Rechercher (marque, modèle, COP…)" style={inputStyle} autoFocus />
          <select value={brand} onChange={e => setBrand(e.target.value)} style={inputStyle}>
            <option value="">Toutes marques</option>
            {brands.map(b => <option key={b} value={b}>{b}</option>)}
          </select>
          <select value={category} onChange={e => setCategory(e.target.value)} style={inputStyle}>
            <option value="">Toutes catégories</option>
            {categories.map(c => <option key={c} value={c}>{c}</option>)}
          </select>
          <button onClick={() => setShowNew(true)} style={{ padding: '7px 12px', fontSize: 12, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 5, fontWeight: 700, whiteSpace: 'nowrap' }}>+ Nouveau</button>
        </div>

        {showNew && <NewProductForm onCancel={() => { setShowNew(false); setSaveState({ loading: false, message: null, error: null }); }} onSave={createProduct} saveState={saveState} categories={categories} />}

        {editMode && draft && (
          <EditProductForm draft={draft} setDraft={setDraft} onCancel={cancelEdit} onSave={saveEdit} onDelete={() => deleteProduct(selected)} saveState={saveState} categories={categories} />
        )}

        {loading && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Recherche…</div>}
        {!loading && results.length === 0 && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Aucun produit trouvé. Les seeds sont chargés au démarrage du serveur. Essaie un autre filtre ou crée un produit manuel.</div>}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {results.map(p => {
            const fostCodes = Array.isArray(p.specs?.fostCodes) ? p.specs.fostCodes : [];
            return (
              <div key={p.id || `${p.brand}-${p.model}`} style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto auto auto auto', gap: 10, padding: '8px 12px', background: selected?.id === p.id ? 'var(--signal-tint)' : 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5, alignItems: 'center', fontSize: 12 }}>
                <span style={{ fontSize: 9, padding: '2px 6px', background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 3, color: 'var(--ink-3)', fontFamily: 'var(--font-mono)' }}>{p.source || 'catalog'}</span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontWeight: 600 }}>{p.brand} — {p.model || p.name} {!p.active && <span style={{ fontSize: 9, color: 'var(--rouge)', marginLeft: 6 }}>archivé</span>}</div>
                  <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>
                    {p.category || '—'}
                    {p.specs?.COP ? ` · COP ${p.specs.COP}` : ''}
                    {p.specs?.SCOP ? ` · SCOP ${p.specs.SCOP}` : ''}
                    {p.energy_class ? ` · ${p.energy_class}` : ''}
                    {fostCodes.length ? ` · FOST: ${fostCodes.join(', ')}` : ''}
                  </div>
                </div>
                {p.price_ht != null && <span className="mono" style={{ fontSize: 11, color: 'var(--signal-deep)' }}>{p.price_ht} € HT</span>}
                <span className="mono" style={{ fontSize: 9, color: 'var(--ink-4)' }}>#{p.id || ''}</span>
                <button onClick={() => startEdit(p)} style={{ fontSize: 10, padding: '3px 8px', background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3, color: 'var(--ink-2)' }}>✎ Éditer</button>
                <button onClick={() => deleteProduct(p)} style={{ fontSize: 10, padding: '3px 8px', background: 'var(--paper)', border: '1px solid var(--rouge-soft)', borderRadius: 3, color: 'var(--rouge)' }}>🗑</button>
              </div>
            );
          })}
        </div>
        <div style={{ marginTop: 12, fontSize: 10, color: 'var(--ink-4)' }}>
          {results.length} résultat{results.length > 1 ? 's' : ''} · sources : EPREL (sync auto dim. 3h), Odoo, seeds internes, manual. Le hard-delete n'est autorisé que sur les produits manuels (sinon désactivation).
        </div>
      </ShellModal>
    );
  }

  // Formulaire création produit manuel (inline dans ProductsModal)
  function NewProductForm({ onCancel, onSave, saveState, categories }) {
    const [f, setF] = useState({
      brand: '', model: '', category: 'autre', sub_category: '', description: '',
      price_ht: '', energy_class: '', fostCodes: '',
      specs: { COP: '', SCOP: '', power_kw: '' },
    });
    const set = (k) => (v) => setF(s => ({ ...s, [k]: v }));
    const setSpec = (k) => (v) => setF(s => ({ ...s, specs: { ...s.specs, [k]: v } }));
    const canSave = f.brand.trim() && f.model.trim();
    return (
      <div style={{ marginBottom: 14, padding: 12, background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 6 }}>
        <div style={{ fontSize: 10, color: 'var(--signal-deep)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 700, marginBottom: 10 }}>Nouveau produit manuel</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
          <EditF label="Marque *" v={f.brand} onChange={set('brand')} />
          <EditF label="Modèle *" v={f.model} onChange={set('model')} />
          <div>
            <span style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Catégorie</span>
            <select value={f.category} onChange={e => set('category')(e.target.value)} style={{ padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', width: '100%' }}>
              <option value="autre">autre</option>
              {categories.map(c => <option key={c} value={c}>{c}</option>)}
            </select>
          </div>
          <EditF label="Sous-catégorie" v={f.sub_category} onChange={set('sub_category')} />
          <EditF label="Classe énergie" v={f.energy_class} onChange={set('energy_class')} />
          <EditF label="Prix HT (€)" v={f.price_ht} onChange={set('price_ht')} type="number" />
          <EditF label="COP" v={f.specs.COP} onChange={setSpec('COP')} type="number" />
          <EditF label="SCOP" v={f.specs.SCOP} onChange={setSpec('SCOP')} type="number" />
          <EditF label="Puissance (kW)" v={f.specs.power_kw} onChange={setSpec('power_kw')} type="number" />
          <EditF label="Codes FOST (BAR-TH-164, BAT-TH-129…)" v={f.fostCodes} onChange={set('fostCodes')} full />
          <EditF label="Description" v={f.description} onChange={set('description')} full textarea />
        </div>
        <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'flex-end' }}>
          {saveState.error && <span style={{ fontSize: 11, color: 'var(--rouge)' }}>⚠ {saveState.error}</span>}
          {saveState.message && <span style={{ fontSize: 11, color: 'var(--signal-deep)' }}>{saveState.message}</span>}
          <button onClick={onCancel} style={{ fontSize: 11, padding: '5px 12px', background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4 }}>Annuler</button>
          <button onClick={() => onSave(f)} disabled={!canSave || saveState.loading} style={{ fontSize: 11, padding: '5px 12px', background: canSave ? 'var(--signal)' : 'var(--paper-3)', color: 'var(--ink)', border: 'none', borderRadius: 4, fontWeight: 700 }}>{saveState.loading ? '…' : '+ Créer'}</button>
        </div>
      </div>
    );
  }

  // Formulaire édition produit (inline dans ProductsModal)
  function EditProductForm({ draft, setDraft, onCancel, onSave, onDelete, saveState, categories }) {
    const set = (k) => (v) => setDraft(d => ({ ...d, [k]: v }));
    const setSpec = (k) => (v) => setDraft(d => ({ ...d, specs: { ...(d.specs || {}), [k]: v } }));
    return (
      <div style={{ marginBottom: 14, padding: 12, background: 'var(--plasma-tint)', border: '1px solid var(--plasma)', borderRadius: 6 }}>
        <div style={{ fontSize: 10, color: 'var(--plasma)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 700, marginBottom: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
          Édition produit #{draft.id}
          <span style={{ fontSize: 9, color: 'var(--ink-4)', fontWeight: 500 }}>source: {draft.source}</span>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
          <EditF label="Marque" v={draft.brand} onChange={set('brand')} />
          <EditF label="Modèle" v={draft.model} onChange={set('model')} />
          <div>
            <span style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Catégorie</span>
            <select value={draft.category} onChange={e => set('category')(e.target.value)} style={{ padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', width: '100%' }}>
              <option value="autre">autre</option>
              {categories.map(c => <option key={c} value={c}>{c}</option>)}
            </select>
          </div>
          <EditF label="Sous-catégorie" v={draft.sub_category} onChange={set('sub_category')} />
          <EditF label="Classe énergie" v={draft.energy_class} onChange={set('energy_class')} />
          <EditF label="Prix HT (€)" v={draft.price_ht} onChange={set('price_ht')} type="number" />
          <EditF label="COP" v={draft.specs?.COP || ''} onChange={setSpec('COP')} type="number" />
          <EditF label="SCOP" v={draft.specs?.SCOP || ''} onChange={setSpec('SCOP')} type="number" />
          <EditF label="Puissance (kW)" v={draft.specs?.power_kw || ''} onChange={setSpec('power_kw')} type="number" />
          <EditF label="Codes FOST (séparés virgule)" v={draft.fostCodes} onChange={set('fostCodes')} full />
          <EditF label="URL fiche produit" v={draft.product_url} onChange={set('product_url')} full />
          <EditF label="Description" v={draft.description} onChange={set('description')} full textarea />
        </div>
        <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'flex-end' }}>
          {saveState.error && <span style={{ fontSize: 11, color: 'var(--rouge)' }}>⚠ {saveState.error}</span>}
          {saveState.message && <span style={{ fontSize: 11, color: 'var(--signal-deep)' }}>{saveState.message}</span>}
          <button onClick={onDelete} style={{ fontSize: 11, padding: '5px 12px', background: 'var(--paper)', border: '1px solid var(--rouge-soft)', borderRadius: 4, color: 'var(--rouge)', marginRight: 'auto' }}>🗑 Supprimer</button>
          <button onClick={onCancel} style={{ fontSize: 11, padding: '5px 12px', background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4 }}>Annuler</button>
          <button onClick={onSave} disabled={saveState.loading} style={{ fontSize: 11, padding: '5px 12px', background: 'var(--plasma)', color: '#fff', border: 'none', borderRadius: 4, fontWeight: 700 }}>{saveState.loading ? '…' : '💾 Enregistrer'}</button>
        </div>
      </div>
    );
  }

  // ────────────────────────────────────────────────────────────
  // OrganizationsModal — tabs Discovery / Odoo partners + sync
  // ────────────────────────────────────────────────────────────
  function OrganizationsModal({ onClose }) {
    const [tab, setTab] = useState('odoo'); // 'odoo' | 'discovery'
    const [orgs, setOrgs] = useState([]);
    const [odooPartners, setOdooPartners] = useState([]);
    const [odooCount, setOdooCount] = useState(0);
    const [selected, setSelected] = useState(null);
    const [contacts, setContacts] = useState([]);
    const [odooContact, setOdooContact] = useState(null);
    const [q, setQ] = useState('');
    const [loadingD, setLoadingD] = useState(true);
    const [loadingO, setLoadingO] = useState(true);
    const [syncState, setSyncState] = useState({ loading: false, message: null });
    const [odooStatus, setOdooStatus] = useState(null);
    // Règle n°5 CRUD : édition inline (orga / odoo partner)
    const [editMode, setEditMode] = useState(false);
    const [editDraft, setEditDraft] = useState({});
    const [saveState, setSaveState] = useState({ loading: false, message: null, error: null });
    // Règle n°5 CRUD : contacts de l'organisation Discovery
    const [selectedContact, setSelectedContact] = useState(null);
    const [contactDraft, setContactDraft] = useState({});
    const [contactEditMode, setContactEditMode] = useState(false);
    const [contactSaveState, setContactSaveState] = useState({ loading: false, message: null, error: null });
    const [showAddContact, setShowAddContact] = useState(false);

    const reloadDiscovery = () => {
      setLoadingD(true);
      fetch(`${API()}/api/organizations`).then(r => r.json()).then(d => { setOrgs(Array.isArray(d) ? d : []); setLoadingD(false); }).catch(() => setLoadingD(false));
    };
    const reloadOdoo = (search) => {
      setLoadingO(true);
      const url = `${API()}/api/odoo/partners${search ? '?search=' + encodeURIComponent(search) + '&limit=200' : '?limit=200'}`;
      fetch(url).then(r => r.json()).then(d => {
        const list = Array.isArray(d) ? d : (d?.partners || d?.records || []);
        setOdooPartners(list);
        setOdooCount(d?.count || d?.total || list.length);
        setLoadingO(false);
      }).catch(() => setLoadingO(false));
    };
    useEffect(() => { reloadDiscovery(); reloadOdoo(); fetch(`${API()}/api/odoo/status`).then(r => r.json()).then(setOdooStatus).catch(() => {}); }, []);

    // Recherche Odoo debounce
    useEffect(() => {
      if (tab !== 'odoo') return;
      const t = setTimeout(() => reloadOdoo(q), 400);
      return () => clearTimeout(t);
    }, [q, tab]);

    // Détail organisation Discovery
    useEffect(() => {
      if (tab !== 'discovery' || !selected) { setContacts([]); return; }
      fetch(`${API()}/api/organizations/${selected.id}`).then(r => r.json()).then(d => setContacts(d?.contacts || [])).catch(() => {});
    }, [selected?.id, tab]);
    // Détail partner Odoo
    useEffect(() => {
      if (tab !== 'odoo' || !selected) { setOdooContact(null); return; }
      fetch(`${API()}/api/odoo/partners/${selected.id}`).then(r => r.json()).then(setOdooContact).catch(() => {});
    }, [selected?.id, tab]);
    // Reset mode édition quand on change de sélection ou d'onglet
    useEffect(() => { setEditMode(false); setEditDraft({}); setSaveState({ loading: false, message: null, error: null }); }, [selected?.id, tab]);

    // ─── Actions édition (Règle n°5 CRUD) ──────────────────────
    const startEdit = () => {
      if (!selected) return;
      if (tab === 'odoo') {
        setEditDraft({
          name: selected.name || '', email: selected.email || '', phone: selected.phone || '',
          mobile: selected.mobile || '', street: selected.street || '', city: selected.city || '',
          zip: selected.zip || '', comment: selected.comment || '',
        });
      } else {
        setEditDraft({
          name: selected.name || '', description: selected.description || '',
          siret: selected.siret || '', address: selected.address || '', city: selected.city || '',
          zip: selected.zip || '', phone: selected.phone || '', email: selected.email || '',
          website: selected.website || '',
        });
      }
      setEditMode(true);
    };
    const cancelEdit = () => { setEditMode(false); setEditDraft({}); setSaveState({ loading: false, message: null, error: null }); };
    const saveEdit = async () => {
      if (!selected) return;
      setSaveState({ loading: true, message: null, error: null });
      try {
        const url = tab === 'odoo' ? `${API()}/api/odoo/partners/${selected.id}` : `${API()}/api/organizations/${selected.id}`;
        const method = tab === 'odoo' ? 'PATCH' : 'PUT';
        const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editDraft) }).then(r => r.json());
        if (res?.error || res?.status === 'error') throw new Error(res.error || res.message || 'Échec');
        // Merge local — pas besoin de refetch, édition optimiste
        const merged = { ...selected, ...editDraft };
        setSelected(merged);
        if (tab === 'odoo') {
          setOdooPartners(ps => ps.map(p => p.id === selected.id ? merged : p));
        } else {
          setOrgs(os => os.map(o => o.id === selected.id ? merged : o));
        }
        setEditMode(false);
        setSaveState({ loading: false, message: '✓ Enregistré', error: null });
        // Déclenche re-hydrate global pour que les autres vues (Kanban, Fiche Dossier) voient la maj
        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 deleteOrg = async () => {
      if (!selected) return;
      if (!window.confirm(`Supprimer définitivement « ${selected.name} » ?`)) return;
      const url = tab === 'odoo' ? null : `${API()}/api/organizations/${selected.id}`;
      if (!url) return; // delete Odoo partner non supporté (protection Odoo)
      await fetch(url, { method: 'DELETE' }).catch(() => {});
      setOrgs(os => os.filter(o => o.id !== selected.id));
      setSelected(null);
    };

    // ─── Actions contacts (Règle n°5 CRUD) ──────────────────────
    const startContactEdit = (c) => {
      setSelectedContact(c);
      setContactDraft({ firstName: c.firstName || '', lastName: c.lastName || '', email: c.email || '', phone: c.phone || '', mobile: c.mobile || '' });
      setContactEditMode(true);
      setShowAddContact(false);
    };
    const cancelContactEdit = () => { setContactEditMode(false); setSelectedContact(null); setContactDraft({}); setContactSaveState({ loading: false, message: null, error: null }); };
    const saveContact = async () => {
      if (!selectedContact) return;
      setContactSaveState({ loading: true, message: null, error: null });
      try {
        const r = await fetch(`${API()}/api/contacts/${selectedContact.id}`, {
          method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(contactDraft),
        }).then(r => r.json());
        if (r.error || r.message?.toLowerCase().includes('not')) throw new Error(r.error || r.message || 'Échec');
        setContacts(cs => cs.map(c => c.id === selectedContact.id ? { ...c, ...contactDraft } : c));
        cancelContactEdit();
        setContactSaveState({ loading: false, message: '✓ Contact mis à jour', error: null });
        setTimeout(() => setContactSaveState({ loading: false, message: null, error: null }), 3000);
      } catch (e) { setContactSaveState({ loading: false, message: null, error: e.message }); }
    };
    const deleteContact = async (c) => {
      if (!window.confirm(`Supprimer le contact « ${c.firstName || ''} ${c.lastName || ''} » ?`)) return;
      await fetch(`${API()}/api/contacts/${c.id}`, { method: 'DELETE' }).catch(() => {});
      setContacts(cs => cs.filter(x => x.id !== c.id));
      if (selectedContact?.id === c.id) cancelContactEdit();
    };
    const createContact = async (draft) => {
      if (!selected) return;
      setContactSaveState({ loading: true, message: null, error: null });
      try {
        const r = await fetch(`${API()}/api/contacts`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ ...draft, organizationId: selected.id }),
        }).then(r => r.json());
        if (r.error) throw new Error(r.error);
        setContacts(cs => [...cs, r]);
        setShowAddContact(false);
        setContactSaveState({ loading: false, message: '✓ Contact créé', error: null });
        setTimeout(() => setContactSaveState({ loading: false, message: null, error: null }), 3000);
      } catch (e) { setContactSaveState({ loading: false, message: null, error: e.message }); }
    };

    const doSyncOdoo = async () => {
      setSyncState({ loading: true, message: null });
      const res = await fetch(`${API()}/api/odoo/sync`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mode: 'incremental' }),
      }).then(r => r.json()).catch(e => ({ ok: false, error: e.message }));
      const msg = res?.ok || res?.status === 'ok' ? `✓ ${res.created || 0} créés · ${res.updated || 0} maj` : `⚠ ${res?.error || res?.message || 'échec'}`;
      setSyncState({ loading: false, message: msg });
      // Refresh après sync
      setTimeout(() => { reloadOdoo(q); setSyncState({ loading: false, message: null }); }, 1500);
    };

    const filteredD = orgs.filter(o => !q || (o.name || '').toLowerCase().includes(q.toLowerCase()));
    const list = tab === 'odoo' ? odooPartners : filteredD;
    const isLoading = tab === 'odoo' ? loadingO : loadingD;

    const renderListItem = (o) => {
      if (tab === 'odoo') {
        const name = o.name || o.display_name || '—';
        return (
          <button key={o.id} onClick={() => setSelected(o)} style={{
            display: 'block', width: '100%', textAlign: 'left', padding: '10px 12px',
            background: selected?.id === o.id ? 'var(--paper-2)' : 'transparent',
            borderLeft: selected?.id === o.id ? '2px solid var(--plasma)' : '2px solid transparent',
            borderBottom: '1px solid var(--hairline)',
          }}>
            <div style={{ fontSize: 12, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }}>
              {o.is_company && <span style={{ fontSize: 9, padding: '1px 5px', background: 'var(--plasma-tint)', color: 'var(--plasma)', borderRadius: 3, fontWeight: 700 }}>SOCIÉTÉ</span>}
              {name}
            </div>
            <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>Odoo #{o.id} · {o.email || o.phone || o.city || '—'}</div>
          </button>
        );
      }
      return (
        <button key={o.id} onClick={() => setSelected(o)} style={{
          display: 'block', width: '100%', textAlign: 'left', padding: '10px 12px',
          background: selected?.id === o.id ? 'var(--paper-2)' : 'transparent',
          borderLeft: selected?.id === o.id ? '2px solid var(--plasma)' : '2px solid transparent',
          borderBottom: '1px solid var(--hairline)',
        }}>
          <div style={{ fontSize: 12, fontWeight: 600 }}>{o.name}</div>
          <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>#{o.id} · {o.description || 'pas de description'}</div>
        </button>
      );
    };

    return (
      <ShellModal
        title="Clients · Contacts"
        subtitle={`${odooCount || odooPartners.length} Odoo · ${orgs.length} Discovery · ${(odooStatus?.status === 'ok' || odooStatus?.connected) ? `● Odoo connecté (uid ${odooStatus.uid || '—'})` : '○ Odoo offline'}`}
        tone="plasma" onClose={onClose} width={1000}
      >
        {/* Toolbar : tabs + sync */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
          <div style={{ display: 'flex', gap: 2, padding: 2, background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 6 }}>
            {[
              { k: 'odoo', l: `Clients Odoo (${odooCount || odooPartners.length})` },
              { k: 'discovery', l: `Organisations Discovery (${orgs.length})` },
            ].map(t => (
              <button key={t.k} onClick={() => { setTab(t.k); setSelected(null); }} style={{
                padding: '4px 12px', fontSize: 12, fontWeight: 500,
                background: tab === t.k ? 'var(--paper)' : 'transparent',
                color: tab === t.k ? 'var(--ink)' : 'var(--ink-3)',
                border: tab === t.k ? '1px solid var(--line)' : '1px solid transparent',
                borderRadius: 4,
              }}>{t.l}</button>
            ))}
          </div>
          <span style={{ flex: 1 }} />
          {tab === 'odoo' && (
            <button onClick={doSyncOdoo} disabled={syncState.loading} style={{
              padding: '6px 12px', fontSize: 12, fontWeight: 500,
              background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 5,
            }}>
              {syncState.loading ? '⏳ Sync Odoo…' : syncState.message || '⟳ Sync Odoo (partners)'}
            </button>
          )}
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: 14, height: '58vh' }}>
          {/* Liste gauche */}
          <div style={{ display: 'flex', flexDirection: 'column', border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden' }}>
            <input value={q} onChange={e => setQ(e.target.value)}
              placeholder={tab === 'odoo' ? 'Chercher client Odoo (nom, email, ville…)' : 'Chercher organisation…'}
              style={{ padding: '8px 12px', fontSize: 12, border: 'none', borderBottom: '1px solid var(--hairline)', background: 'var(--paper-2)' }} />
            <div style={{ flex: 1, overflowY: 'auto' }}>
              {isLoading && <div style={{ padding: 10, fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>}
              {!isLoading && list.length === 0 && <div style={{ padding: 10, fontSize: 11, color: 'var(--ink-4)' }}>
                Aucun résultat. {tab === 'odoo' && 'Clique sur « Sync Odoo » pour rafraîchir.'}
              </div>}
              {list.map(renderListItem)}
            </div>
          </div>
          {/* Détail droite */}
          <div style={{ display: 'flex', flexDirection: 'column', overflow: 'auto' }}>
            {!selected && <div style={{ display: 'grid', placeItems: 'center', height: '100%', color: 'var(--ink-4)', fontSize: 12 }}>Sélectionne un client</div>}
            {selected && (
              <>
                {/* Header avec bouton édition */}
                <div style={{ borderBottom: '1px solid var(--hairline)', paddingBottom: 10, marginBottom: 10, display: 'flex', alignItems: 'flex-start', gap: 10 }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 16, fontWeight: 700 }}>{selected.name || selected.display_name}</div>
                    <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>
                      {tab === 'odoo' ? `Odoo partner #${selected.id} ${selected.is_company ? '· société' : '· contact'}` : `Organisation Discovery #${selected.id}`}
                    </div>
                  </div>
                  {!editMode ? (
                    <div style={{ display: 'flex', gap: 6 }}>
                      <button onClick={startEdit} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 4, fontWeight: 500 }}>✏️ Éditer</button>
                      {tab === 'discovery' && (
                        <button onClick={deleteOrg} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 4, fontWeight: 500 }}>🗑 Supprimer</button>
                      )}
                    </div>
                  ) : (
                    <div style={{ display: 'flex', gap: 6 }}>
                      <button onClick={cancelEdit} 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>
                    </div>
                  )}
                </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>}

                {/* Mode Odoo */}
                {tab === 'odoo' && !editMode && (
                  <>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 16 }}>
                      <Kv label="Email" v={selected.email} />
                      <Kv label="Téléphone" v={selected.phone || selected.mobile} />
                      <Kv label="Ville" v={selected.city} />
                      <Kv label="Code postal" v={selected.zip} />
                      <Kv label="Pays" v={selected.country_id?.[1] || selected.country || '—'} />
                      <Kv label="VAT / SIRET" v={selected.vat} />
                      <Kv label="Site web" v={selected.website} />
                      <Kv label="Référence" v={selected.ref} />
                    </div>
                    {selected.street && (
                      <div style={{ marginBottom: 12, padding: 10, background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5, fontSize: 11 }}>
                        <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Adresse</div>
                        <div>{selected.street}{selected.street2 ? ', ' + selected.street2 : ''}</div>
                        <div>{selected.zip} {selected.city}</div>
                      </div>
                    )}
                    <div style={{ marginTop: 12, padding: 10, background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 5, fontSize: 10, color: 'var(--ink-3)' }}>
                      💡 Client Odoo. Utilise « Nouveau dossier » pour créer une opportunité liée à ce partner #{selected.id}.
                    </div>
                  </>
                )}
                {tab === 'odoo' && editMode && (
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 16 }}>
                    <EditF label="Nom" v={editDraft.name} onChange={v => setEditDraft(d => ({ ...d, name: v }))} full />
                    <EditF label="Email" v={editDraft.email} onChange={v => setEditDraft(d => ({ ...d, email: v }))} type="email" />
                    <EditF label="Téléphone" v={editDraft.phone} onChange={v => setEditDraft(d => ({ ...d, phone: v }))} />
                    <EditF label="Mobile" v={editDraft.mobile} onChange={v => setEditDraft(d => ({ ...d, mobile: v }))} />
                    <EditF label="Adresse" v={editDraft.street} onChange={v => setEditDraft(d => ({ ...d, street: v }))} full />
                    <EditF label="Code postal" v={editDraft.zip} onChange={v => setEditDraft(d => ({ ...d, zip: v }))} />
                    <EditF label="Ville" v={editDraft.city} onChange={v => setEditDraft(d => ({ ...d, city: v }))} />
                    <EditF label="Commentaire" v={editDraft.comment} onChange={v => setEditDraft(d => ({ ...d, comment: v }))} full textarea />
                  </div>
                )}

                {/* Mode Discovery */}
                {tab === 'discovery' && !editMode && (
                  <>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 16 }}>
                      <Kv label="Description" v={selected.description} />
                      <Kv label="SIRET" v={selected.siret} />
                      <Kv label="Email" v={selected.email} />
                      <Kv label="Téléphone" v={selected.phone} />
                      <Kv label="Adresse" v={selected.address} />
                      <Kv label="Code postal" v={selected.zip} />
                      <Kv label="Ville" v={selected.city} />
                      <Kv label="Site web" v={selected.website} />
                    </div>
                    <div style={{ display: 'flex', alignItems: 'center', marginBottom: 6 }}>
                      <span style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600 }}>Contacts liés ({contacts.length})</span>
                      <span style={{ flex: 1 }} />
                      <button onClick={() => { setShowAddContact(v => !v); cancelContactEdit(); }} style={{ fontSize: 10, padding: '3px 8px', background: 'var(--signal-tint)', color: 'var(--signal-deep)', border: '1px solid var(--signal-soft)', borderRadius: 3, fontWeight: 600 }}>
                        {showAddContact ? '✕ Annuler' : '+ Ajouter'}
                      </button>
                    </div>
                    {/* Feedback contact */}
                    {contactSaveState.message && <div style={{ marginBottom: 8, padding: '5px 8px', fontSize: 10, background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 4, color: 'var(--signal-deep)' }}>{contactSaveState.message}</div>}
                    {contactSaveState.error && <div style={{ marginBottom: 8, padding: '5px 8px', fontSize: 10, background: 'var(--rouge-tint)', border: '1px solid var(--rouge)', borderRadius: 4, color: 'var(--rouge)' }}>⚠ {contactSaveState.error}</div>}
                    {/* Formulaire ajout contact */}
                    {showAddContact && <AddContactForm onSave={createContact} busy={contactSaveState.loading} />}
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
                      {contacts.length === 0 && !showAddContact && <div style={{ fontSize: 11, color: 'var(--ink-4)', padding: '8px 0' }}>Aucun contact lié. Clique « + Ajouter » pour en créer un.</div>}
                      {contacts.map(c => (
                        <div key={c.id} style={{ padding: '8px 10px', background: selectedContact?.id === c.id && contactEditMode ? 'var(--paper)' : 'var(--paper-2)', border: '1px solid ' + (selectedContact?.id === c.id ? 'var(--signal-soft)' : 'var(--hairline)'), borderRadius: 5 }}>
                          {/* En-tête contact */}
                          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                            <div style={{ flex: 1, minWidth: 0 }}>
                              <div style={{ fontSize: 11, fontWeight: 600 }}>{c.firstName || ''} {c.lastName || ''}</div>
                              <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{c.email || '—'} {c.phone ? '· ' + c.phone : ''}</div>
                            </div>
                            {!(selectedContact?.id === c.id && contactEditMode) && (
                              <div style={{ display: 'flex', gap: 4, flexShrink: 0 }}>
                                <button onClick={() => startContactEdit(c)} style={{ fontSize: 9, padding: '2px 6px', background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3 }}>✏️</button>
                                <button onClick={() => deleteContact(c)} style={{ fontSize: 9, padding: '2px 6px', background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 3 }}>🗑</button>
                              </div>
                            )}
                          </div>
                          {/* Formulaire édition contact inline */}
                          {selectedContact?.id === c.id && contactEditMode && (
                            <div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
                              <EditF label="Prénom" v={contactDraft.firstName} onChange={v => setContactDraft(d => ({ ...d, firstName: v }))} />
                              <EditF label="Nom" v={contactDraft.lastName} onChange={v => setContactDraft(d => ({ ...d, lastName: v }))} />
                              <EditF label="Email" v={contactDraft.email} onChange={v => setContactDraft(d => ({ ...d, email: v }))} type="email" />
                              <EditF label="Téléphone" v={contactDraft.phone} onChange={v => setContactDraft(d => ({ ...d, phone: v }))} />
                              <EditF label="Mobile" v={contactDraft.mobile} onChange={v => setContactDraft(d => ({ ...d, mobile: v }))} />
                              <div style={{ gridColumn: '1 / -1', display: 'flex', gap: 6, justifyContent: 'flex-end', marginTop: 4 }}>
                                <button onClick={cancelContactEdit} disabled={contactSaveState.loading} style={{ fontSize: 10, padding: '4px 8px', background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 3 }}>Annuler</button>
                                <button onClick={saveContact} disabled={contactSaveState.loading} style={{ fontSize: 10, padding: '4px 10px', background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 3, fontWeight: 600 }}>{contactSaveState.loading ? '…' : '✓ Sauver'}</button>
                              </div>
                            </div>
                          )}
                        </div>
                      ))}
                    </div>
                  </>
                )}
                {tab === 'discovery' && editMode && (
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 16 }}>
                    <EditF label="Nom" v={editDraft.name} onChange={v => setEditDraft(d => ({ ...d, name: v }))} full />
                    <EditF label="SIRET" v={editDraft.siret} onChange={v => setEditDraft(d => ({ ...d, siret: v }))} />
                    <EditF label="Email" v={editDraft.email} onChange={v => setEditDraft(d => ({ ...d, email: v }))} type="email" />
                    <EditF label="Téléphone" v={editDraft.phone} onChange={v => setEditDraft(d => ({ ...d, phone: v }))} />
                    <EditF label="Site web" v={editDraft.website} onChange={v => setEditDraft(d => ({ ...d, website: v }))} full />
                    <EditF label="Adresse" v={editDraft.address} onChange={v => setEditDraft(d => ({ ...d, address: v }))} full />
                    <EditF label="Code postal" v={editDraft.zip} onChange={v => setEditDraft(d => ({ ...d, zip: v }))} />
                    <EditF label="Ville" v={editDraft.city} onChange={v => setEditDraft(d => ({ ...d, city: v }))} />
                    <EditF label="Description" v={editDraft.description} onChange={v => setEditDraft(d => ({ ...d, description: v }))} full textarea />
                  </div>
                )}
              </>
            )}
          </div>
        </div>
      </ShellModal>
    );
  }

  // Helper Key-value pour le détail Odoo
  function Kv({ label, v }) {
    return (
      <div>
        <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{label}</div>
        <div style={{ fontSize: 12, color: v ? 'var(--ink)' : 'var(--ink-4)', fontWeight: v ? 500 : 400 }}>{v || '—'}</div>
      </div>
    );
  }
  // Helper champ éditable (single/full-width, input/textarea)
  function EditF({ label, v, onChange, type, textarea, full }) {
    const baseStyle = { 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' };
    return (
      <div style={{ gridColumn: full ? '1 / -1' : 'auto', display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
        <span style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{label}</span>
        {textarea
          ? <textarea value={v || ''} onChange={e => onChange(e.target.value)} rows={2} style={{ ...baseStyle, resize: 'vertical' }} />
          : <input type={type || 'text'} value={v || ''} onChange={e => onChange(e.target.value)} style={baseStyle} />}
      </div>
    );
  }

  // Helper — formulaire ajout contact inline dans OrganizationsModal
  function AddContactForm({ onSave, busy }) {
    const [f, setF] = useState({ firstName: '', lastName: '', email: '', phone: '' });
    const set = (k) => (v) => setF(s => ({ ...s, [k]: v }));
    return (
      <div style={{ marginBottom: 10, padding: 10, background: 'var(--paper)', border: '1px solid var(--signal-soft)', borderRadius: 5 }}>
        <div style={{ fontSize: 9, color: 'var(--signal-deep)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 700, marginBottom: 8 }}>Nouveau contact</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
          <EditF label="Prénom" v={f.firstName} onChange={set('firstName')} />
          <EditF label="Nom" v={f.lastName} onChange={set('lastName')} />
          <EditF label="Email" v={f.email} onChange={set('email')} type="email" />
          <EditF label="Téléphone" v={f.phone} onChange={set('phone')} />
          <div style={{ gridColumn: '1 / -1', display: 'flex', justifyContent: 'flex-end', gap: 6, marginTop: 4 }}>
            <button onClick={() => onSave(f)} disabled={busy || (!f.firstName && !f.email)} style={{ fontSize: 10, padding: '4px 12px', background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 3, fontWeight: 700 }}>{busy ? '…' : '+ Créer contact'}</button>
          </div>
        </div>
      </div>
    );
  }

  // ────────────────────────────────────────────────────────────
  // DevisDrawer — devis Odoo + création avec pré-remplissage
  // ────────────────────────────────────────────────────────────
  function DevisDrawer({ onClose, prefillContext }) {
    const [quotes, setQuotes] = useState([]);
    const [loading, setLoading] = useState(true);
    const [filter, setFilter] = useState('');

    useEffect(() => {
      fetch(`${API()}/api/odoo/quotes`).then(r => r.json()).then(d => { setQuotes(d?.quotes || d || []); setLoading(false); }).catch(() => setLoading(false));
    }, []);

    const filtered = quotes.filter(q => !filter || (q.name || q.reference || '').toLowerCase().includes(filter.toLowerCase()) || (q.partner_name || q.partnerName || '').toLowerCase().includes(filter.toLowerCase()));

    return (
      <ShellModal title="Devis Odoo" subtitle={`${quotes.length} devis · synchronisés depuis Odoo`} tone="amber" onClose={onClose} width={900}>
        {prefillContext && (
          <div style={{ padding: 10, marginBottom: 12, background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 5, fontSize: 11 }}>
            <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Contexte de création</div>
            <div style={{ color: 'var(--ink-2)' }}>{prefillContext.description || prefillContext.partnerName || '—'}</div>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 2 }}>kind: {prefillContext.kind || '—'} · montant: {prefillContext.amount || 0} €</div>
          </div>
        )}
        <input value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filtrer par nom ou partenaire…" style={{ padding: '8px 12px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', marginBottom: 12, width: '100%' }} />
        {loading && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {filtered.slice(0, 50).map(q => (
            <div key={q.id} style={{ display: 'grid', gridTemplateColumns: '90px 1fr auto auto', gap: 10, padding: '8px 12px', background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5, fontSize: 12, alignItems: 'center' }}>
              <span className="mono" style={{ fontSize: 10 }}>{q.name || q.reference || '—'}</span>
              <div>
                <div style={{ fontWeight: 500 }}>{q.partner_name || q.partnerName || '—'}</div>
                <div style={{ fontSize: 9, color: 'var(--ink-4)' }}>{q.date_order || q.dateOpen || ''} · {q.state || q.stage || ''}</div>
              </div>
              <span className="mono" style={{ fontSize: 11, color: 'var(--signal-deep)' }}>{q.amount_total || q.amount || 0} €</span>
              <a href={q.externalUrl || '#'} target="_blank" rel="noreferrer" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{q.externalUrl ? '↗ Odoo' : ''}</a>
            </div>
          ))}
          {!loading && filtered.length === 0 && <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Aucun devis trouvé. Lance un « Sync Odoo » depuis Dossiers CEE.</div>}
        </div>
      </ShellModal>
    );
  }

  // ────────────────────────────────────────────────────────────
  // CommunicationDrawer — inbox IMAP
  // ────────────────────────────────────────────────────────────
  function CommunicationDrawer({ onClose }) {
    const [messages, setMessages] = useState([]);
    const [loading, setLoading] = useState(true);
    const [status, setStatus] = useState(null);
    const [selected, setSelected] = useState(null);

    useEffect(() => {
      fetch(`${API()}/api/comm/status`).then(r => r.json()).then(setStatus).catch(() => {});
      fetch(`${API()}/api/comm/inbox?page=1&limit=30`).then(r => r.json()).then(d => { setMessages(d?.messages || d?.emails || []); setLoading(false); }).catch(() => setLoading(false));
    }, []);

    return (
      <ShellModal title="Communication" subtitle={status ? `IMAP ${status.watching ? '● connecté' : '○ hors-ligne'} · ${status.total || messages.length} messages` : 'Inbox IMAP'} tone="plasma" onClose={onClose} width={1000}>
        <div style={{ display: 'grid', gridTemplateColumns: '340px 1fr', gap: 14, height: '58vh' }}>
          <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
            <div style={{ padding: '6px 10px', borderBottom: '1px solid var(--hairline)', fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{messages.length} récents</div>
            <div style={{ flex: 1, overflowY: 'auto' }}>
              {loading && <div style={{ padding: 10, fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>}
              {messages.map(m => (
                <button key={m.id} onClick={() => setSelected(m)} style={{
                  display: 'block', width: '100%', textAlign: 'left',
                  padding: '10px 12px', borderBottom: '1px solid var(--hairline)',
                  background: selected?.id === m.id ? 'var(--paper-2)' : 'transparent',
                  borderLeft: m.senderType === 'boss' ? '2px solid var(--rouge)' : m.senderType === 'client' ? '2px solid var(--signal)' : '2px solid transparent',
                }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                    {m.senderType === 'boss' && <span style={{ fontSize: 9, padding: '1px 5px', background: 'var(--rouge)', color: '#fff', borderRadius: 3, fontWeight: 700 }}>BOSS</span>}
                    {m.priority === 'high' && <span style={{ fontSize: 9, color: 'var(--rouge)' }}>●</span>}
                    <span style={{ fontSize: 11, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{m.fromName || m.fromAddress}</span>
                    <span className="mono" style={{ fontSize: 9, color: 'var(--ink-4)' }}>{m.status || ''}</span>
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--ink-2)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.subject || '(sans objet)'}</div>
                  <div style={{ fontSize: 9, color: 'var(--ink-4)', marginTop: 1 }}>{m.receivedAt ? new Date(m.receivedAt).toLocaleString('fr-FR') : ''}</div>
                </button>
              ))}
            </div>
          </div>
          <div style={{ overflow: 'auto' }}>
            {!selected && <div style={{ display: 'grid', placeItems: 'center', height: '100%', color: 'var(--ink-4)', fontSize: 12 }}>Sélectionne un email</div>}
            {selected && (
              <div>
                <div style={{ fontSize: 16, fontWeight: 700 }}>{selected.subject || '(sans objet)'}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2, marginBottom: 12 }}>De <b>{selected.fromName || selected.fromAddress}</b> · reçu le {selected.receivedAt ? new Date(selected.receivedAt).toLocaleString('fr-FR') : ''}</div>
                <div style={{ fontSize: 12, lineHeight: 1.6, color: 'var(--ink-2)', whiteSpace: 'pre-wrap' }}>{selected.bodyText || selected.body || selected.snippet || 'Corps non disponible'}</div>
                {Array.isArray(selected.attachments) && selected.attachments.length > 0 && (
                  <div style={{ marginTop: 14, padding: 10, background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5 }}>
                    <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 6 }}>{selected.attachments.length} pièce(s) jointe(s)</div>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                      {selected.attachments.map(a => (
                        <a key={a.id} href={`${API()}/api/comm/emails/${selected.id}/attachments/${a.id}/download`} target="_blank" rel="noreferrer" style={{ fontSize: 11, color: 'var(--ink-3)' }}>📎 {a.filename} ({Math.round((a.size || 0) / 1024)} KB)</a>
                      ))}
                    </div>
                  </div>
                )}
              </div>
            )}
          </div>
        </div>
      </ShellModal>
    );
  }

  // ────────────────────────────────────────────────────────────
  // Registre global
  // ────────────────────────────────────────────────────────────
  window.ProductsModal = ProductsModal;
  window.OrganizationsModal = OrganizationsModal;
  window.DevisDrawer = DevisDrawer;
  window.CommunicationDrawer = CommunicationDrawer;
})();
