/* global React */
// CalendarPage — Calendrier RDV commercial + visites techniques + deadlines
// Vue semaine (défaut) et vue mois. Création RDV via modal.

const KIND_META = {
  rdv_commercial:   { label: 'RDV commercial',     color: '#3b82f6', bg: 'rgba(59,130,246,.15)' },
  visite_technique: { label: 'Visite technique',   color: '#f97316', bg: 'rgba(249,115,22,.15)' },
  pre_visite:       { label: 'Pré-visite terrain', color: '#f59e0b', bg: 'rgba(245,158,11,.15)' },
  relance:          { label: 'Relance',             color: '#8b5cf6', bg: 'rgba(139,92,246,.15)' },
  deadline:         { label: 'Deadline dossier',   color: '#ef4444', bg: 'rgba(239,68,68,.15)'  },
  signature:        { label: 'Signature',           color: '#10b981', bg: 'rgba(16,185,129,.15)' },
  autre:            { label: 'Autre',               color: '#6b7280', bg: 'rgba(107,114,128,.15)' },
};

const DAYS_FR  = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
const MONTHS_FR = ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'];

function isoDate(d) {
  return d.toISOString().slice(0, 10);
}
function startOfWeek(date) {
  const d = new Date(date);
  const day = d.getDay(); // 0=Sun
  const diff = day === 0 ? -6 : 1 - day;
  d.setDate(d.getDate() + diff);
  d.setHours(0, 0, 0, 0);
  return d;
}
function startOfMonth(date) {
  return new Date(date.getFullYear(), date.getMonth(), 1);
}
function endOfMonth(date) {
  return new Date(date.getFullYear(), date.getMonth() + 1, 0);
}
function addDays(date, n) {
  const d = new Date(date);
  d.setDate(d.getDate() + n);
  return d;
}
function fmtHHMM(date) {
  const d = new Date(date);
  return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
}
function fmtShort(date) {
  return new Date(date).toLocaleDateString('fr-FR', { day:'2-digit', month:'short' });
}

// ─── Modal création / édition RDV ───────────────────────────────────────────
function AppointmentModal({ initial, onSave, onClose }) {
  const BASE = (window.AE_API && window.AE_API.BASE) || '';
  const prefs = loadPrefs();
  const defaultKind = initial?.kind || prefs.defaultKind || 'rdv_commercial';
  const [form, setForm] = useState({
    title: '', kind: defaultKind,
    scheduledAt: initial?.scheduledAt ? new Date(initial.scheduledAt).toISOString().slice(0,16) : new Date().toISOString().slice(0,16),
    durationMin: prefs.durationByKind[defaultKind] || 60,
    clientName: '', location: '', notes: '', assignedTo: '',
    contactId: null, organizationId: null, opportunityId: null,
    recurrence: null, recurrenceEnd: '',
    ...(initial || {}),
  });
  const [saving, setSaving] = useState(false);
  const [picked, setPicked] = useState(null);
  const [conflicts, setConflicts] = useState([]);  // [{id, title, scheduledAt, durationMin}]

  // Détection conflits : appel debounced quand date/durée/assigné changent
  useEffect(() => {
    if (!form.scheduledAt) return;
    const timer = setTimeout(async () => {
      try {
        const r = await fetch(`${BASE}/api/calendar/appointments`).then(x => x.json());
        if (!r.ok) return;
        const start = new Date(form.scheduledAt).getTime();
        const end = start + (form.durationMin || 60) * 60_000;
        const cf = (r.appointments || []).filter(a => {
          if (initial?.id && a.id === initial.id) return false; // s'ignore en édition
          if (a.status === 'cancelled') return false;
          // Conflit prioritaire sur même assigné
          if (form.assignedTo && a.assignedTo && form.assignedTo !== a.assignedTo) return false;
          const aStart = new Date(a.scheduledAt).getTime();
          const aEnd = aStart + (a.durationMin || 60) * 60_000;
          return start < aEnd && end > aStart; // overlap
        });
        setConflicts(cf);
      } catch (_) {}
    }, 300);
    return () => clearTimeout(timer);
  }, [form.scheduledAt, form.durationMin, form.assignedTo]);

  // À chaque changement de kind → durée par défaut du type (si pas édition)
  const set = (k, v) => {
    setForm(p => {
      const next = { ...p, [k]: v };
      if (k === 'kind' && !initial?.id) {
        next.durationMin = prefs.durationByKind[v] || 60;
        if (!p.title && prefs.titleTemplates[v]) {
          next.title = prefs.titleTemplates[v].replace('{client}', p.clientName || '').replace('{dossier}', '').trim();
        }
      }
      return next;
    });
  };

  // Upsert via ClientPicker → propage contactId/organizationId et pré-remplit adresse
  const handlePickClient = async (c) => {
    setPicked(c);
    if (!c) {
      set('contactId', null); set('organizationId', null); set('clientName', '');
      return;
    }
    let contactId = c.contactId || null;
    let organizationId = c.organizationId || null;
    // Upsert Contact AE si vient d'Odoo ou nouveau
    if (!contactId && (c.email || c.name)) {
      try {
        const r = await fetch(`${BASE}/api/contacts`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            firstName: c.firstName || c.name?.split(' ')[0] || '',
            lastName:  c.lastName  || c.name?.split(' ').slice(1).join(' ') || '',
            email: c.email || null, phone: c.phone || c.mobile || null,
            odooPartnerId: c.odooId || null, organizationId: c.organizationId || null,
          }),
        }).then(x => x.json());
        if (r?.id) contactId = r.id;
        if (r?.organizationId) organizationId = r.organizationId;
      } catch (_) {}
    }
    setForm(p => ({
      ...p,
      contactId, organizationId,
      clientName: c.name || p.clientName,
      location: p.location || [c.street, c.zip, c.city].filter(Boolean).join(' '),
      title: p.title || `RDV — ${c.name}`,
    }));
  };

  const handleSave = async () => {
    if (!form.title.trim()) { alert('Titre requis'); return; }
    if (conflicts.length > 0) {
      const list = conflicts.map(c => `• ${c.title} (${new Date(c.scheduledAt).toLocaleString('fr-FR')})`).join('\n');
      const ok = window.confirm(`⚠ Conflit de créneau détecté avec ${conflicts.length} RDV :\n\n${list}\n\nEnregistrer quand même ?`);
      if (!ok) return;
    }
    setSaving(true);
    try {
      const isEdit = !!initial?.id;
      const url  = isEdit ? `${BASE}/api/calendar/appointments/${initial.id}` : `${BASE}/api/calendar/appointments`;
      const meth = isEdit ? 'PATCH' : 'POST';
      const r = await fetch(url, {
        method: meth,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...form, scheduledAt: new Date(form.scheduledAt).toISOString() }),
      }).then(x => x.json());
      if (r.ok) { onSave(r.appointment); }
      else { alert(r.error || 'Erreur'); }
    } finally { setSaving(false); }
  };

  const handleDelete = async () => {
    if (!initial?.id) return;
    if (!window.confirm('Supprimer ce RDV ?')) return;
    await fetch(`${BASE}/api/calendar/appointments/${initial.id}`, { method: 'DELETE' });
    onSave(null, 'delete');
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center' }} onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ background: 'var(--paper)', borderRadius: 12, width: 460, boxShadow: '0 20px 60px rgba(0,0,0,.25)', overflow: 'hidden' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '14px 18px', borderBottom: '1px solid var(--hairline)' }}>
          <Icon.calendarPlus style={{ color: 'var(--ink-3)' }} />
          <span style={{ fontWeight: 600, fontSize: 14 }}>{initial?.id ? 'Modifier le RDV' : 'Nouveau RDV'}</span>
          <span style={{ flex: 1 }} />
          {initial?.id && <button onClick={handleDelete} style={{ color: 'var(--rouge)', fontSize: 12, padding: '3px 8px', border: '1px solid rgba(239,68,68,.3)', borderRadius: 5 }}>Supprimer</button>}
          <button onClick={onClose} style={{ color: 'var(--ink-4)' }}><Icon.cross /></button>
        </div>
        <div style={{ padding: '16px 18px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          {/* Titre */}
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Titre *</label>
            <input value={form.title} onChange={e => set('title', e.target.value)}
              placeholder="Ex : RDV découverte M. Dupont"
              style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 13, background: 'var(--paper-2)', color: 'var(--ink)', boxSizing: 'border-box' }} />
          </div>
          {/* Type */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Type</label>
              <select value={form.kind} onChange={e => set('kind', e.target.value)}
                style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)' }}>
                {Object.entries(KIND_META).map(([k, m]) => <option key={k} value={k}>{m.label}</option>)}
              </select>
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Durée (min)</label>
              <select value={form.durationMin} onChange={e => set('durationMin', Number(e.target.value))}
                style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)' }}>
                {[15,30,45,60,90,120,180,240].map(v => <option key={v} value={v}>{v} min</option>)}
              </select>
            </div>
          </div>
          {/* Date/heure */}
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Date & heure *</label>
            <input type="datetime-local" value={form.scheduledAt} onChange={e => set('scheduledAt', e.target.value)}
              style={{
                width: '100%', padding: '7px 10px',
                border: '1px solid', borderColor: conflicts.length > 0 ? 'var(--amber)' : 'var(--line)',
                borderRadius: 6, fontSize: 13,
                background: conflicts.length > 0 ? 'rgba(245,158,11,.06)' : 'var(--paper-2)',
                color: 'var(--ink)', boxSizing: 'border-box',
              }} />
            {conflicts.length > 0 && (
              <div style={{ marginTop: 6, padding: '6px 10px', background: 'rgba(245,158,11,.1)', border: '1px solid var(--amber)', borderRadius: 5, fontSize: 11, color: 'var(--amber)' }}>
                ⚠ Conflit avec {conflicts.length} RDV{conflicts.length > 1 ? 's' : ''} :
                <ul style={{ margin: '4px 0 0 14px', padding: 0, color: 'var(--ink-3)' }}>
                  {conflicts.slice(0, 3).map(c => (
                    <li key={c.id}>{c.title} · {new Date(c.scheduledAt).toLocaleString('fr-FR', { hour: '2-digit', minute: '2-digit', day: '2-digit', month: 'short' })}</li>
                  ))}
                  {conflicts.length > 3 && <li>… +{conflicts.length - 3} autre{conflicts.length - 3 > 1 ? 's' : ''}</li>}
                </ul>
              </div>
            )}
          </div>
          {/* Client (ClientPicker — règle n°3.a) */}
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Client</label>
            {window.ClientPicker ? (
              <window.ClientPicker value={picked} onChange={handlePickClient} placeholder="Rechercher client Odoo / AE…" compact />
            ) : (
              <div style={{ fontSize: 11, color: 'var(--ink-4)', padding: '6px 10px', border: '1px dashed var(--line)', borderRadius: 6 }}>
                ClientPicker indisponible — rechargez la page
              </div>
            )}
            {form.contactId && (
              <div className="mono" style={{ fontSize: 10, color: 'var(--signal-deep)', marginTop: 4 }}>
                ✓ lié contactId={form.contactId}{form.organizationId ? ` · orgId=${form.organizationId}` : ''}
              </div>
            )}
          </div>
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Assigné à</label>
            <input value={form.assignedTo} onChange={e => set('assignedTo', e.target.value)}
              placeholder="Collaborateur"
              style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)', boxSizing: 'border-box' }} />
          </div>
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Lieu / adresse</label>
            <input value={form.location} onChange={e => set('location', e.target.value)}
              placeholder="Adresse ou lien visio"
              style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)', boxSizing: 'border-box' }} />
          </div>
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>Notes</label>
            <textarea value={form.notes} onChange={e => set('notes', e.target.value)}
              rows={2} placeholder="Objectifs, contexte…"
              style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)', resize: 'vertical', boxSizing: 'border-box' }} />
          </div>

          {/* Récurrence */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>↻ Récurrence</label>
              <select value={form.recurrence || ''} onChange={e => set('recurrence', e.target.value || null)}
                style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)', boxSizing: 'border-box' }}>
                <option value="">Pas de récurrence</option>
                <option value="daily">Tous les jours</option>
                <option value="weekly">Toutes les semaines</option>
                <option value="biweekly">Toutes les 2 semaines</option>
                <option value="monthly">Tous les mois</option>
              </select>
            </div>
            <div>
              <label style={{ fontSize: 11, color: 'var(--ink-4)', display: 'block', marginBottom: 4 }}>
                Fin de récurrence {!form.recurrence && <span style={{ color: 'var(--ink-5)' }}>(N/A)</span>}
              </label>
              <input type="date" value={form.recurrenceEnd ? String(form.recurrenceEnd).slice(0, 10) : ''}
                onChange={e => set('recurrenceEnd', e.target.value || null)}
                disabled={!form.recurrence}
                placeholder="Jamais"
                style={{ width: '100%', padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: form.recurrence ? 'var(--paper-2)' : 'var(--paper-3)', color: 'var(--ink)', boxSizing: 'border-box', opacity: form.recurrence ? 1 : 0.5 }} />
            </div>
          </div>
        </div>
        <div style={{ padding: '12px 18px', borderTop: '1px solid var(--hairline)', display: 'flex', justifyContent: 'flex-end', gap: 8, alignItems: 'center' }}>
          {initial?.id && (
            <a href={`${BASE}/api/calendar/appointments/${initial.id}/ics`} download
              title="Télécharger ce RDV au format iCal"
              style={{ padding: '6px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 11, background: 'var(--paper-2)', color: 'var(--ink-3)', textDecoration: 'none', marginRight: 'auto' }}>
              ⬇ .ics
            </a>
          )}
          <button onClick={onClose} style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 13, background: 'var(--paper-2)', color: 'var(--ink-3)' }}>Annuler</button>
          <button onClick={handleSave} disabled={saving}
            style={{ padding: '7px 16px', border: 'none', borderRadius: 6, fontSize: 13, fontWeight: 600, background: 'var(--ink)', color: 'var(--paper)', opacity: saving ? 0.6 : 1 }}>
            {saving ? 'Enregistrement…' : (initial?.id ? 'Mettre à jour' : 'Créer le RDV')}
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── Pastille événement ──────────────────────────────────────────────────────
function EventChip({ ev, onClick, draggable, collabColors }) {
  const meta = KIND_META[ev.kind] || KIND_META.autre;
  // Couleur prioritaire : collaborateur > ev.color > kind
  const collabColor = collabColors && ev.assignedTo ? collabColors[ev.assignedTo] : null;
  const finalColor = collabColor || ev.color || meta.color;
  const onDragStart = (e) => {
    if (!draggable || ev.source !== 'appointment') return;
    try {
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('application/x-ae-appointment', JSON.stringify({
        id: ev.id, durationMin: ev.durationMin || 60, originalStart: ev.start,
      }));
    } catch (_) {}
  };
  return (
    <button onClick={() => onClick(ev)}
      draggable={draggable && ev.source === 'appointment'}
      onDragStart={onDragStart}
      title={ev.assignedTo ? `Assigné à ${ev.assignedTo}` : undefined}
      style={{
        display: 'block', width: '100%', textAlign: 'left',
        padding: '3px 6px', marginBottom: 2, borderRadius: 4,
        background: collabColor ? collabColor + '22' : (ev.color ? ev.color + '22' : meta.bg),
        borderLeft: `3px solid ${finalColor}`,
        fontSize: 11, lineHeight: 1.3, cursor: draggable && ev.source === 'appointment' ? 'grab' : 'pointer',
    }}>
      <div style={{ fontWeight: 600, color: finalColor, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
        {ev.source === 'appointment' ? fmtHHMM(ev.start) + ' ' : ''}{ev.title}
      </div>
      {ev.clientName && <div style={{ fontSize: 10, color: 'var(--ink-3)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ev.clientName}</div>}
    </button>
  );
}

// ─── Vue semaine / jour (grille horaire) ─────────────────────────────────────
// Si `days` fourni, l'utilise tel quel (ex: 1 jour pour vue jour). Sinon dérive
// 7 jours depuis `weekStart`.
function WeekView({ weekStart, days: daysProp, events, onNewAt, onClickEvent, onMoveEvent, dayStart = 8, dayEnd = 20, collabColors }) {
  const [dragOver, setDragOver] = useState(null); // {day, h}
  const handleDragOver = (e, day, h) => {
    if (!onMoveEvent) return;
    if (!Array.from(e.dataTransfer.types || []).includes('application/x-ae-appointment')) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    setDragOver(`${isoDate(day)}-${h}`);
  };
  const handleDrop = (e, day, h) => {
    if (!onMoveEvent) return;
    setDragOver(null);
    try {
      const raw = e.dataTransfer.getData('application/x-ae-appointment');
      if (!raw) return;
      const data = JSON.parse(raw);
      const newDate = new Date(day);
      newDate.setHours(h, new Date(data.originalStart).getMinutes(), 0, 0);
      e.preventDefault();
      onMoveEvent(data.id, newDate.toISOString());
    } catch (_) {}
  };
  const days = daysProp && daysProp.length
    ? daysProp
    : Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
  const colTemplate = `48px repeat(${days.length}, 1fr)`;
  const hourCount = Math.max(1, dayEnd - dayStart + 1);
  const hours = Array.from({ length: hourCount }, (_, i) => i + dayStart);

  return (
    <div style={{ display: 'grid', gridTemplateColumns: colTemplate, flex: 1, overflowY: 'auto', minHeight: 0 }}>
      {/* Header jours */}
      <div style={{ borderBottom: '1px solid var(--line)', gridColumn: '1 / -1', display: 'grid', gridTemplateColumns: colTemplate }}>
        <div />
        {days.map((day, i) => {
          const isToday = isoDate(day) === isoDate(new Date());
          // En vue jour : libellé long ; en vue semaine : libellé court
          const label = days.length === 1
            ? day.toLocaleDateString('fr-FR', { weekday: 'long', day: '2-digit', month: 'long' })
            : DAYS_FR[day.getDay() === 0 ? 6 : day.getDay() - 1];
          return (
            <div key={i} style={{ padding: '8px 6px', textAlign: 'center', borderLeft: '1px solid var(--hairline)' }}>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6 }}>{label}</div>
              {days.length > 1 && (
                <div style={{
                  fontSize: 18, fontWeight: 700, color: isToday ? 'var(--paper)' : 'var(--ink)',
                  width: 28, height: 28, borderRadius: '50%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginTop: 2,
                  background: isToday ? 'var(--ink)' : 'transparent',
                }}>{day.getDate()}</div>
              )}
            </div>
          );
        })}
      </div>

      {/* Grille horaire */}
      {hours.map(h => (
        <React.Fragment key={h}>
          <div style={{ borderBottom: '1px solid var(--hairline)', padding: '2px 6px 0', textAlign: 'right' }}>
            <span className="mono" style={{ fontSize: 10, color: 'var(--ink-5)' }}>{String(h).padStart(2,'0')}h</span>
          </div>
          {days.map((day, di) => {
            const iso = isoDate(day);
            const slotEvs = events.filter(ev => {
              const ed = new Date(ev.start);
              return isoDate(ed) === iso && ed.getHours() === h;
            });
            const slotKey = `${iso}-${h}`;
            const isDropTarget = dragOver === slotKey;
            return (
              <div key={di}
                onClick={() => { const d = new Date(day); d.setHours(h, 0, 0, 0); onNewAt(d); }}
                onDragOver={(e) => handleDragOver(e, day, h)}
                onDragLeave={() => setDragOver(null)}
                onDrop={(e) => handleDrop(e, day, h)}
                style={{
                  borderLeft: '1px solid var(--hairline)', borderBottom: '1px solid var(--hairline)',
                  minHeight: 36, padding: '2px 3px', cursor: 'default', position: 'relative',
                  background: isDropTarget ? 'rgba(34,197,94,.12)' : 'transparent',
                  outline: isDropTarget ? '2px dashed var(--signal-deep)' : 'none',
                  outlineOffset: -2,
                }}>
                {slotEvs.map((ev, idx) => <EventChip key={ev.id || idx} ev={ev} onClick={onClickEvent} draggable={!!onMoveEvent} collabColors={collabColors} />)}
              </div>
            );
          })}
        </React.Fragment>
      ))}
    </div>
  );
}

// ─── Vue Liste — agenda linéaire 60 prochains jours, groupés par jour ────────
function ListView({ events, onClickEvent }) {
  const now = new Date();
  // Tous les events futurs ou aujourd'hui, triés ASC
  const future = events
    .filter(ev => new Date(ev.start) >= new Date(now.getFullYear(), now.getMonth(), now.getDate()))
    .sort((a, b) => new Date(a.start) - new Date(b.start));

  // Group by day
  const grouped = {};
  future.forEach(ev => {
    const k = isoDate(new Date(ev.start));
    if (!grouped[k]) grouped[k] = [];
    grouped[k].push(ev);
  });
  const days = Object.keys(grouped).sort();

  if (days.length === 0) {
    return (
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-4)', fontSize: 13 }}>
        Aucun événement à venir sur la période.
      </div>
    );
  }

  return (
    <div style={{ flex: 1, overflowY: 'auto', padding: '12px 24px' }}>
      {days.map(k => {
        const d = new Date(k);
        const isToday = isoDate(d) === isoDate(new Date());
        const dayLabel = d.toLocaleDateString('fr-FR', { weekday: 'long', day: '2-digit', month: 'long' });
        return (
          <div key={k} style={{ marginBottom: 18 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
              <div style={{
                width: 32, height: 32, borderRadius: 6,
                background: isToday ? 'var(--ink)' : 'var(--paper-2)',
                color: isToday ? 'var(--paper)' : 'var(--ink-2)',
                display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                fontSize: 14, fontWeight: 700, lineHeight: 1,
                border: '1px solid var(--line)',
              }}>
                {d.getDate()}
              </div>
              <div>
                <div style={{ fontSize: 13, fontWeight: 600, textTransform: 'capitalize' }}>{dayLabel}</div>
                <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
                  {grouped[k].length} événement{grouped[k].length > 1 ? 's' : ''}{isToday ? ' · aujourd\'hui' : ''}
                </div>
              </div>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 5, paddingLeft: 42 }}>
              {grouped[k].map((ev, idx) => {
                const meta = KIND_META[ev.kind] || KIND_META.autre;
                return (
                  <button key={ev.id || idx} onClick={() => onClickEvent && onClickEvent(ev)}
                    style={{
                      display: 'grid', gridTemplateColumns: '54px 6px 1fr auto', gap: 10, alignItems: 'center',
                      padding: '8px 12px', border: '1px solid var(--line)', borderRadius: 6,
                      background: 'var(--paper)', cursor: 'pointer', textAlign: 'left',
                      transition: 'all 100ms',
                    }}
                    onMouseEnter={e => { e.currentTarget.style.background = 'var(--paper-2)'; e.currentTarget.style.borderColor = 'var(--line-2)'; }}
                    onMouseLeave={e => { e.currentTarget.style.background = 'var(--paper)'; e.currentTarget.style.borderColor = 'var(--line)'; }}>
                    <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)', fontWeight: 600 }}>
                      {fmtHHMM(ev.start)}
                    </span>
                    <span style={{ width: 4, height: 32, borderRadius: 2, background: ev.color || meta.color }} />
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {ev.title}
                      </div>
                      <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 2 }}>
                        {meta.label} · {ev.durationMin || 60} min
                        {ev.clientName ? ` · ${ev.clientName}` : ''}
                        {ev.location ? ` · ${ev.location}` : ''}
                      </div>
                    </div>
                    <span style={{ fontSize: 10, color: 'var(--ink-5)' }}>→</span>
                  </button>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ─── Vue mois ────────────────────────────────────────────────────────────────
function MonthView({ monthStart, events, onNewAt, onClickEvent }) {
  const mEnd = endOfMonth(monthStart);
  const gridStart = startOfWeek(monthStart);
  // Remplir jusqu'à dimanche après fin du mois
  const gridEnd   = startOfWeek(mEnd);
  const weeks = [];
  let cur = gridStart;
  while (cur <= addDays(gridEnd, 6)) {
    const week = Array.from({ length: 7 }, (_, i) => addDays(cur, i));
    weeks.push(week);
    cur = addDays(cur, 7);
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
      {/* Entêtes */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', borderBottom: '1px solid var(--line)' }}>
        {DAYS_FR.map(d => (
          <div key={d} className="mono" style={{ padding: '6px 8px', fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6 }}>{d}</div>
        ))}
      </div>
      {/* Semaines */}
      {weeks.map((week, wi) => (
        <div key={wi} style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', flex: 1, borderBottom: '1px solid var(--hairline)' }}>
          {week.map((day, di) => {
            const iso = isoDate(day);
            const dayEvs = events.filter(ev => isoDate(new Date(ev.start)) === iso);
            const isToday = iso === isoDate(new Date());
            const isOtherMonth = day.getMonth() !== monthStart.getMonth();
            return (
              <div key={di}
                onClick={() => onNewAt(day)}
                style={{ borderLeft: di > 0 ? '1px solid var(--hairline)' : 'none', padding: '4px 5px', minHeight: 80, cursor: 'default', background: isOtherMonth ? 'var(--paper-2)' : 'transparent' }}>
                <div style={{
                  width: 22, height: 22, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 12, fontWeight: isToday ? 700 : 400,
                  color: isToday ? 'var(--paper)' : isOtherMonth ? 'var(--ink-5)' : 'var(--ink)',
                  background: isToday ? 'var(--ink)' : 'transparent',
                  marginBottom: 2,
                }}>{day.getDate()}</div>
                {dayEvs.slice(0, 3).map((ev, idx) => <EventChip key={ev.id || idx} ev={ev} onClick={onClickEvent} />)}
                {dayEvs.length > 3 && <div style={{ fontSize: 10, color: 'var(--ink-4)', paddingLeft: 4 }}>+{dayEvs.length - 3} autres</div>}
              </div>
            );
          })}
        </div>
      ))}
    </div>
  );
}

// ─── Légende + filtres ───────────────────────────────────────────────────────
function Legend({ activeKinds, onToggle }) {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
      {Object.entries(KIND_META).map(([k, m]) => {
        const active = activeKinds.includes(k);
        return (
          <button key={k} onClick={() => onToggle(k)} style={{
            display: 'flex', alignItems: 'center', gap: 5,
            padding: '3px 8px', borderRadius: 20, fontSize: 11, fontWeight: 500,
            border: `1px solid ${active ? m.color : 'var(--line)'}`,
            background: active ? m.bg : 'transparent',
            color: active ? m.color : 'var(--ink-4)',
            cursor: 'pointer', transition: 'all 120ms',
          }}>
            <span style={{ width: 7, height: 7, borderRadius: '50%', background: active ? m.color : 'var(--ink-5)' }} />
            {m.label}
          </button>
        );
      })}
    </div>
  );
}

// ─── Onglet Personnalisation ─────────────────────────────────────────────────
const DEFAULT_PREFS = {
  workDayStart: 8, workDayEnd: 20,
  defaultKind: 'rdv_commercial',
  durationByKind: { rdv_commercial: 60, visite_technique: 120, pre_visite: 90, relance: 30, deadline: 30, signature: 30, autre: 60 },
  colorByKind: Object.fromEntries(Object.entries(KIND_META).map(([k, m]) => [k, m.color])),
  titleTemplates: { rdv_commercial: 'RDV découverte — {client}', visite_technique: 'Visite tech. — {client}', pre_visite: 'Pré-visite — {client}', relance: 'Relance — {client}', deadline: 'Deadline — {dossier}', signature: 'Signature — {client}', autre: '' },
  notifyEmail: false, notifyDayBefore: true, notifyHourBefore: false,
  syncGoogle: false, syncOutlook: false,
  // Couleur par collaborateur (assignedTo) — prioritaire sur kind si présent
  // Format : { 'Nils Bazin': '#3b82f6', 'Valentin Roque': '#10b981', ... }
  colorByCollaborator: {},
};

function loadPrefs() {
  try { return { ...DEFAULT_PREFS, ...JSON.parse(localStorage.getItem('ae_cal_prefs') || '{}') }; }
  catch { return DEFAULT_PREFS; }
}
function savePrefs(p) {
  try { localStorage.setItem('ae_cal_prefs', JSON.stringify(p)); } catch (_) {}
}

// ─── Onglet Stats — KPI calendrier ───────────────────────────────────────────
function StatsTab() {
  const BASE = (window.AE_API && window.AE_API.BASE) || '';
  const [appts, setAppts] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch(`${BASE}/api/calendar/appointments`).then(r => r.json())
      .then(d => { if (d.ok) setAppts(d.appointments || []); })
      .finally(() => setLoading(false));
  }, []);

  const now = new Date();
  const last30Start = new Date(now.getTime() - 30 * 86400_000);
  const last7Start  = new Date(now.getTime() -  7 * 86400_000);

  const inLast30 = appts.filter(a => new Date(a.scheduledAt) >= last30Start && new Date(a.scheduledAt) <= now);
  const inLast7  = appts.filter(a => new Date(a.scheduledAt) >= last7Start  && new Date(a.scheduledAt) <= now);
  const upcoming = appts.filter(a => new Date(a.scheduledAt) > now && a.status === 'scheduled');
  const cancelled = appts.filter(a => a.status === 'cancelled');
  const cancelRate = appts.length > 0 ? Math.round((cancelled.length / appts.length) * 100) : 0;

  const avgDuration = appts.length > 0
    ? Math.round(appts.reduce((s, a) => s + (a.durationMin || 60), 0) / appts.length)
    : 0;

  // Distribution par kind
  const byKind = {};
  appts.forEach(a => { byKind[a.kind || 'autre'] = (byKind[a.kind || 'autre'] || 0) + 1; });
  const kindEntries = Object.entries(byKind).sort((a, b) => b[1] - a[1]);
  const totalForDist = appts.length || 1;

  // Top 5 clients
  const byClient = {};
  appts.forEach(a => {
    if (!a.clientName) return;
    byClient[a.clientName] = (byClient[a.clientName] || 0) + 1;
  });
  const topClients = Object.entries(byClient).sort((a, b) => b[1] - a[1]).slice(0, 5);

  // Sparkline 30 derniers jours
  const dailyCount = Array.from({ length: 30 }, (_, i) => {
    const dayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() - (29 - i));
    const dayEnd   = new Date(dayStart.getTime() + 86400_000);
    return appts.filter(a => new Date(a.scheduledAt) >= dayStart && new Date(a.scheduledAt) < dayEnd).length;
  });
  const maxDay = Math.max(1, ...dailyCount);

  // Top 3 collaborateurs (assignedTo)
  const byCollab = {};
  appts.forEach(a => { if (a.assignedTo) byCollab[a.assignedTo] = (byCollab[a.assignedTo] || 0) + 1; });
  const topCollabs = Object.entries(byCollab).sort((a, b) => b[1] - a[1]).slice(0, 3);

  const Card = ({ label, value, sub, color = 'var(--ink)' }) => (
    <div style={{ padding: '14px 16px', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
      <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600 }}>{label}</div>
      <div style={{ fontSize: 26, fontWeight: 700, color, marginTop: 4 }}>{value}</div>
      {sub && <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>{sub}</div>}
    </div>
  );

  return (
    <div style={{ padding: '20px 24px', maxWidth: 1100, flex: 1, overflowY: 'auto' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
        <h2 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>Statistiques calendrier</h2>
        {loading && <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>chargement…</span>}
      </div>

      {/* 4 KPI principales */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 16 }}>
        <Card label="RDV 7 derniers jours"  value={inLast7.length}  sub={`${inLast30.length} sur 30j`} />
        <Card label="RDV à venir"            value={upcoming.length} sub={upcoming.filter(a => new Date(a.scheduledAt) - now < 86400_000).length + " aujourd'hui"} />
        <Card label="Taux annulation"        value={`${cancelRate}%`} sub={`${cancelled.length} annulés / ${appts.length} total`} color={cancelRate > 20 ? 'var(--rouge)' : cancelRate > 10 ? 'var(--amber)' : 'var(--signal-deep)'} />
        <Card label="Durée moyenne"          value={`${avgDuration} min`} sub={`Total : ${Math.round(appts.reduce((s,a)=>s+(a.durationMin||60),0)/60)}h`} />
      </div>

      {/* Sparkline activité 30 jours */}
      <div style={{ padding: '14px 16px', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', marginBottom: 16 }}>
        <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8 }}>Activité — 30 derniers jours</div>
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: 80 }}>
          {dailyCount.map((c, i) => (
            <div key={i} title={`${c} RDV`} style={{
              flex: 1, height: `${Math.max(4, (c / maxDay) * 100)}%`,
              background: c > 0 ? 'var(--signal)' : 'var(--paper-3)',
              borderRadius: '2px 2px 0 0',
            }} />
          ))}
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 9, color: 'var(--ink-5)', marginTop: 4 }}>
          <span>il y a 30j</span>
          <span>aujourd'hui</span>
        </div>
      </div>

      {/* 2 colonnes : distribution par kind + top clients */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <div style={{ padding: '14px 16px', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
          <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10 }}>Répartition par type</div>
          {kindEntries.length === 0 ? (
            <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Aucun RDV</div>
          ) : kindEntries.map(([k, n]) => {
            const meta = KIND_META[k] || KIND_META.autre;
            const pct = Math.round((n / totalForDist) * 100);
            return (
              <div key={k} style={{ marginBottom: 8 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, marginBottom: 3 }}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', background: meta.color }} />
                  <span style={{ flex: 1 }}>{meta.label}</span>
                  <span className="mono" style={{ color: 'var(--ink-4)' }}>{n} · {pct}%</span>
                </div>
                <div style={{ height: 4, background: 'var(--paper-3)', borderRadius: 2 }}>
                  <div style={{ height: '100%', width: `${pct}%`, background: meta.color, borderRadius: 2 }} />
                </div>
              </div>
            );
          })}
        </div>

        <div style={{ padding: '14px 16px', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)' }}>
          <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 10 }}>Top 5 clients par RDV</div>
          {topClients.length === 0 ? (
            <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Aucun client identifié</div>
          ) : topClients.map(([name, n], i) => (
            <div key={name} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0', borderBottom: i < topClients.length - 1 ? '1px solid var(--hairline)' : 'none' }}>
              <span className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', width: 16 }}>#{i + 1}</span>
              <span style={{ flex: 1, fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</span>
              <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>{n}</span>
            </div>
          ))}

          {topCollabs.length > 0 && (
            <>
              <div style={{ fontSize: 12, fontWeight: 600, marginTop: 14, marginBottom: 8 }}>Top collaborateurs</div>
              {topCollabs.map(([name, n], i) => (
                <div key={name} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0' }}>
                  <span className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', width: 16 }}>#{i + 1}</span>
                  <span style={{ flex: 1, fontSize: 11 }}>{name}</span>
                  <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{n} RDV</span>
                </div>
              ))}
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function CustomizationTab() {
  const [prefs, setPrefs] = useState(() => loadPrefs());
  const [saved, setSaved] = useState(false);

  const update = (path, value) => {
    setPrefs(prev => {
      const next = JSON.parse(JSON.stringify(prev));
      const parts = path.split('.');
      let cur = next;
      for (let i = 0; i < parts.length - 1; i++) cur = cur[parts[i]];
      cur[parts[parts.length - 1]] = value;
      return next;
    });
    setSaved(false);
  };

  const handleSave = () => { savePrefs(prefs); setSaved(true); setTimeout(() => setSaved(false), 2000); };
  const handleReset = () => { if (window.confirm('Restaurer les valeurs par défaut ?')) { setPrefs(DEFAULT_PREFS); savePrefs(DEFAULT_PREFS); } };

  return (
    <div style={{ padding: '20px 24px', maxWidth: 900, flex: 1, overflowY: 'auto' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 20 }}>
        <h2 style={{ fontSize: 16, fontWeight: 700, margin: 0 }}>Personnalisation du calendrier</h2>
        <span style={{ flex: 1 }} />
        {saved && <span style={{ fontSize: 11, color: 'var(--signal-deep)' }}>✓ enregistré</span>}
        <button onClick={handleReset} style={{ fontSize: 11, color: 'var(--ink-4)', padding: '5px 10px', border: '1px solid var(--line)', borderRadius: 6 }}>Réinitialiser</button>
        <button onClick={handleSave} style={{ padding: '6px 14px', borderRadius: 6, background: 'var(--ink)', color: 'var(--paper)', fontSize: 12, fontWeight: 600, border: 'none' }}>Enregistrer</button>
      </div>

      {/* Horaires */}
      <section style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 16, marginBottom: 14 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>Horaires de travail (vue semaine)</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)' }}>Début journée</label>
            <select value={prefs.workDayStart} onChange={e => update('workDayStart', Number(e.target.value))}
              style={{ width: '100%', marginTop: 4, padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper)', color: 'var(--ink)' }}>
              {Array.from({ length: 13 }, (_, i) => i + 5).map(h => <option key={h} value={h}>{String(h).padStart(2,'0')}h00</option>)}
            </select>
          </div>
          <div>
            <label style={{ fontSize: 11, color: 'var(--ink-4)' }}>Fin journée</label>
            <select value={prefs.workDayEnd} onChange={e => update('workDayEnd', Number(e.target.value))}
              style={{ width: '100%', marginTop: 4, padding: '7px 10px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper)', color: 'var(--ink)' }}>
              {Array.from({ length: 11 }, (_, i) => i + 14).map(h => <option key={h} value={h}>{String(h).padStart(2,'0')}h00</option>)}
            </select>
          </div>
        </div>
      </section>

      {/* Durées + couleurs par type */}
      <section style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 16, marginBottom: 14 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>Durées et couleurs par type</div>
        <div style={{ display: 'grid', gap: 6 }}>
          {Object.entries(KIND_META).map(([k, meta]) => (
            <div key={k} style={{ display: 'grid', gridTemplateColumns: '180px 120px 60px 1fr', gap: 10, alignItems: 'center', padding: '6px 0', borderBottom: '1px solid var(--hairline)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: prefs.colorByKind[k] || meta.color }} />
                <span style={{ fontSize: 12 }}>{meta.label}</span>
              </div>
              <select value={prefs.durationByKind[k]} onChange={e => update(`durationByKind.${k}`, Number(e.target.value))}
                style={{ padding: '5px 8px', border: '1px solid var(--line)', borderRadius: 5, fontSize: 11, background: 'var(--paper)', color: 'var(--ink)' }}>
                {[15,30,45,60,90,120,180,240].map(v => <option key={v} value={v}>{v} min</option>)}
              </select>
              <input type="color" value={prefs.colorByKind[k] || meta.color} onChange={e => update(`colorByKind.${k}`, e.target.value)}
                style={{ width: 40, height: 24, border: '1px solid var(--line)', borderRadius: 4, cursor: 'pointer', background: 'transparent' }} />
              <input value={prefs.titleTemplates[k] || ''} onChange={e => update(`titleTemplates.${k}`, e.target.value)}
                placeholder="Modèle de titre (ex : RDV — {client})"
                style={{ padding: '5px 8px', border: '1px solid var(--line)', borderRadius: 5, fontSize: 11, background: 'var(--paper)', color: 'var(--ink)', boxSizing: 'border-box' }} />
            </div>
          ))}
        </div>
        <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 8 }}>Jetons : {'{client}'}, {'{dossier}'}, {'{date}'} — remplacés à la création</div>
      </section>

      {/* Notifications */}
      <section style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 16, marginBottom: 14 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>Rappels & notifications</div>
        {[
          ['notifyDayBefore', 'Rappel la veille du RDV'],
          ['notifyHourBefore', 'Rappel 1h avant le RDV'],
          ['notifyEmail', 'Envoyer les rappels par email (SMTP configuré)'],
        ].map(([k, l]) => (
          <label key={k} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0', fontSize: 12 }}>
            <input type="checkbox" checked={!!prefs[k]} onChange={e => update(k, e.target.checked)} />
            {l}
          </label>
        ))}
      </section>

      {/* Couleurs par collaborateur */}
      <section style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 16, marginBottom: 14 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <span style={{ fontSize: 13, fontWeight: 600 }}>Couleur par collaborateur</span>
          <span style={{ flex: 1 }} />
          <button onClick={() => {
              const name = window.prompt('Nom du collaborateur (tel qu\'apparaissant dans "Assigné à")');
              if (!name) return;
              const trimmed = name.trim();
              if (!trimmed) return;
              update(`colorByCollaborator.${trimmed}`, '#3b82f6');
            }}
            style={{ fontSize: 11, padding: '4px 10px', border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper)', color: 'var(--ink-2)', fontWeight: 500 }}>
            + Ajouter
          </button>
        </div>
        <div style={{ fontSize: 10, color: 'var(--ink-4)', marginBottom: 10 }}>
          Surcharge la couleur du type quand le RDV est assigné à ce collaborateur (utile en équipe).
        </div>
        {Object.keys(prefs.colorByCollaborator || {}).length === 0 ? (
          <div style={{ padding: '14px 0', textAlign: 'center', fontSize: 11, color: 'var(--ink-4)' }}>
            Aucun collaborateur configuré — cliquez "+ Ajouter".
          </div>
        ) : Object.entries(prefs.colorByCollaborator).map(([name, color]) => (
          <div key={name} style={{ display: 'grid', gridTemplateColumns: '1fr 60px 28px', gap: 10, alignItems: 'center', padding: '6px 0', borderBottom: '1px solid var(--hairline)' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ width: 10, height: 10, borderRadius: '50%', background: color, border: '1px solid var(--line)' }} />
              <span style={{ fontSize: 12 }}>{name}</span>
            </div>
            <input type="color" value={color} onChange={e => update(`colorByCollaborator.${name}`, e.target.value)}
              style={{ width: 50, height: 24, border: '1px solid var(--line)', borderRadius: 4, cursor: 'pointer' }} />
            <button onClick={() => {
                setPrefs(prev => {
                  const next = JSON.parse(JSON.stringify(prev));
                  delete next.colorByCollaborator[name];
                  return next;
                });
                setSaved(false);
              }}
              title="Retirer" style={{ fontSize: 14, color: 'var(--rouge)', padding: 2 }}>×</button>
          </div>
        ))}
      </section>

      {/* Intégrations externes */}
      <section style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 16, marginBottom: 14 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 10 }}>Synchronisation externe</div>
        {[
          ['syncGoogle', 'Google Calendar', 'Synchro bidirectionnelle (à configurer dans Intégrations)'],
          ['syncOutlook', 'Outlook / Microsoft 365', 'Synchro bidirectionnelle (à configurer dans Intégrations)'],
        ].map(([k, l, sub]) => (
          <div key={k} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0', borderBottom: '1px solid var(--hairline)' }}>
            <input type="checkbox" checked={!!prefs[k]} onChange={e => update(k, e.target.checked)} disabled />
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 12, fontWeight: 500 }}>{l}</div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{sub}</div>
            </div>
            <span className="mono" style={{ fontSize: 9, padding: '2px 6px', borderRadius: 4, background: 'var(--paper-3)', color: 'var(--ink-4)' }}>BACKLOG</span>
          </div>
        ))}
      </section>

      {/* Export */}
      <section style={{ border: '1px solid var(--line)', borderRadius: 8, background: 'var(--paper-2)', padding: 16 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>Export</div>
        <div style={{ fontSize: 11, color: 'var(--ink-4)', marginBottom: 10 }}>
          Téléchargez tous vos RDV au format iCal — importable dans Google Calendar, Outlook, Apple Calendar.
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <a href={`${(window.AE_API && window.AE_API.BASE) || ''}/api/calendar/export.ics`}
            download
            style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper)', color: 'var(--ink)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, fontWeight: 500 }}>
            ⬇ Export iCal (.ics) — tous les RDV
          </a>
          <a href={`${(window.AE_API && window.AE_API.BASE) || ''}/api/calendar/export.ics?from=${new Date().toISOString()}&to=${new Date(Date.now() + 30 * 86400_000).toISOString()}`}
            download
            style={{ padding: '7px 14px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper)', color: 'var(--ink)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, fontWeight: 500 }}>
            ⬇ Export iCal — 30 prochains jours
          </a>
          <button disabled style={{ padding: '7px 12px', border: '1px solid var(--line)', borderRadius: 6, fontSize: 12, background: 'var(--paper)', color: 'var(--ink-4)', opacity: 0.6 }}>
            Export CSV · bientôt
          </button>
        </div>
      </section>
    </div>
  );
}

// ─── Page principale ─────────────────────────────────────────────────────────
function CalendarPage() {
  const BASE = (window.AE_API && window.AE_API.BASE) || '';
  const [tab, setTab]           = useState('calendar');   // 'calendar' | 'custom'
  const [view, setView]         = useState('week');       // 'week' | 'month'
  const [anchor, setAnchor]     = useState(new Date());   // date d'ancrage
  const [events, setEvents]     = useState([]);
  const [loading, setLoading]   = useState(false);
  const [modal, setModal]       = useState(null);         // null | { initial, defaultDate }
  const [activeKinds, setActive] = useState(Object.keys(KIND_META));
  const [prefs, setPrefs]       = useState(() => loadPrefs());
  // Rafraîchir prefs quand on revient de l'onglet Personnalisation
  useEffect(() => { if (tab === 'calendar') setPrefs(loadPrefs()); }, [tab]);

  const weekStart  = startOfWeek(anchor);
  const monthStart = startOfMonth(anchor);

  const fetchEvents = async () => {
    setLoading(true);
    let from, to;
    if (view === 'list') {
      from = new Date(); from.setHours(0,0,0,0);
      to   = new Date(from.getTime() + 60 * 86400_000); // 60 jours à venir
    } else if (view === 'day') {
      from = new Date(anchor); from.setHours(0,0,0,0);
      to   = new Date(from.getTime() + 86400_000);
    } else if (view === 'week') {
      from = weekStart;
      to   = addDays(weekStart, 7);
    } else {
      from = startOfWeek(monthStart);
      to   = addDays(startOfWeek(endOfMonth(monthStart)), 7);
    }
    try {
      const r = await fetch(`${BASE}/api/calendar/events?from=${from.toISOString()}&to=${to.toISOString()}`).then(x => x.json());
      if (r.ok) setEvents(r.events || []);
    } catch (_) {}
    setLoading(false);
  };

  useEffect(() => { fetchEvents(); }, [view, isoDate(view === 'week' ? weekStart : (view === 'day' ? anchor : monthStart))]);

  // Pre-select RDV si on arrive ici via clic sur notification (P1 — règle n°3)
  useEffect(() => {
    const tgt = window.AE_NOTIF_TARGET;
    if (!tgt || tgt.kind !== 'appointment' || !tgt.id) return;
    (async () => {
      try {
        const r = await fetch(`${BASE}/api/calendar/appointments`).then(x => x.json());
        const appt = (r.appointments || []).find(a => String(a.id) === String(tgt.id));
        if (appt) {
          // Recadrer la vue sur la date du RDV
          setAnchor(new Date(appt.scheduledAt));
          // Ouvrir le modal d'édition
          setModal({ initial: { ...appt, source: 'appointment' } });
        }
      } catch (_) {}
      try { delete window.AE_NOTIF_TARGET; } catch (_) {}
    })();
  }, []);

  const visibleEvents = events.filter(ev => activeKinds.includes(ev.kind));

  const handleToggleKind = (k) => setActive(prev =>
    prev.includes(k) ? prev.filter(x => x !== k) : [...prev, k]
  );

  const handleNewAt = (date) => {
    const d = new Date(date);
    d.setMinutes(0, 0, 0);
    setModal({ initial: { scheduledAt: d.toISOString() } });
  };

  const handleClickEvent = (ev) => {
    if (ev.source === 'appointment') {
      setModal({ initial: ev });
    }
  };

  const handleSave = (appt, action) => {
    setModal(null);
    fetchEvents();
  };

  // Drag & drop : replanifier un RDV via PATCH (appel optimiste)
  const handleMoveEvent = async (apptId, newScheduledAt) => {
    // Optimiste : update visuel immédiat
    setEvents(prev => prev.map(ev =>
      (ev.source === 'appointment' && ev.id === apptId)
        ? { ...ev, start: newScheduledAt }
        : ev
    ));
    try {
      const r = await fetch(`${BASE}/api/calendar/appointments/${apptId}`, {
        method: 'PATCH', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ scheduledAt: newScheduledAt }),
      }).then(x => x.json());
      if (!r.ok) throw new Error(r.error || 'Échec replanification');
    } catch (e) {
      // Rollback en re-fetch
      fetchEvents();
      alert(`Erreur replanification : ${e.message}`);
    }
  };

  const navPrev = () => {
    if (view === 'day')   setAnchor(a => addDays(a, -1));
    else if (view === 'week')  setAnchor(a => addDays(a, -7));
    else setAnchor(a => { const d = new Date(a); d.setMonth(d.getMonth() - 1); return d; });
  };
  const navNext = () => {
    if (view === 'day')   setAnchor(a => addDays(a, 1));
    else if (view === 'week')  setAnchor(a => addDays(a, 7));
    else setAnchor(a => { const d = new Date(a); d.setMonth(d.getMonth() + 1); return d; });
  };
  const navToday = () => setAnchor(new Date());

  const headerLabel = view === 'list'
    ? '60 prochains jours'
    : view === 'day'
      ? anchor.toLocaleDateString('fr-FR', { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' })
      : view === 'week'
        ? `${fmtShort(weekStart)} — ${fmtShort(addDays(weekStart, 6))} ${weekStart.getFullYear()}`
        : `${MONTHS_FR[monthStart.getMonth()]} ${monthStart.getFullYear()}`;

  // Prochains RDV (sidebar)
  const upcomingAppts = events
    .filter(ev => new Date(ev.start) >= new Date() && ev.source === 'appointment')
    .slice(0, 8);

  return (
    <div style={{ display: 'flex', height: 'calc(100vh - 52px)', minWidth: 0 }}>
      {/* Contenu principal */}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, overflow: 'hidden' }}>
        {/* Header avec onglets */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '10px 20px 0', borderBottom: '1px solid var(--line)', flexShrink: 0 }}>
          <h1 style={{ fontSize: 18, fontWeight: 700, letterSpacing: '-0.02em', margin: 0 }}>Calendrier</h1>
          <div style={{ display: 'flex', gap: 2, marginLeft: 10 }}>
            {[['calendar','Calendrier'],['stats','Statistiques'],['custom','Personnalisation']].map(([k, l]) => (
              <button key={k} onClick={() => setTab(k)} style={{
                padding: '8px 14px 10px', fontSize: 12, fontWeight: tab === k ? 600 : 500,
                color: tab === k ? 'var(--ink)' : 'var(--ink-4)',
                background: 'transparent', borderBottom: tab === k ? '2px solid var(--ink)' : '2px solid transparent',
                marginBottom: -1, cursor: 'pointer',
              }}>{l}</button>
            ))}
          </div>
          <span style={{ flex: 1 }} />
          {tab === 'calendar' && (
            <button onClick={() => setModal({ initial: { scheduledAt: new Date().toISOString() } })}
              style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 14px', borderRadius: 7, background: 'var(--ink)', color: 'var(--paper)', fontSize: 13, fontWeight: 600, border: 'none', cursor: 'pointer', marginBottom: 8 }}>
              <Icon.calendarPlus style={{ width: 13, height: 13 }} /> Nouveau RDV
            </button>
          )}
        </div>

        {tab === 'stats' ? <StatsTab /> : tab === 'custom' ? <CustomizationTab /> : (
        <React.Fragment>
        {/* Toolbar calendrier */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 20px', borderBottom: '1px solid var(--hairline)', flexShrink: 0 }}>
          {/* Navigation */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
            <button onClick={navPrev} style={{ padding: '5px 8px', border: '1px solid var(--line)', borderRadius: '6px 0 0 6px', background: 'var(--paper-2)', color: 'var(--ink-3)', cursor: 'pointer' }}>‹</button>
            <button onClick={navToday} style={{ padding: '5px 10px', borderTop: '1px solid var(--line)', borderBottom: '1px solid var(--line)', background: 'var(--paper-2)', fontSize: 12, color: 'var(--ink-3)', cursor: 'pointer' }}>Aujourd'hui</button>
            <button onClick={navNext} style={{ padding: '5px 8px', border: '1px solid var(--line)', borderRadius: '0 6px 6px 0', background: 'var(--paper-2)', color: 'var(--ink-3)', cursor: 'pointer' }}>›</button>
          </div>

          <span style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink-2)', minWidth: 200 }}>{headerLabel}</span>
          {loading && <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>chargement…</span>}

          <span style={{ flex: 1 }} />

          {/* Vue toggle */}
          <div style={{ display: 'flex', border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden' }}>
            {[['day','Jour'],['week','Semaine'],['month','Mois'],['list','Liste']].map(([k, l], idx, arr) => (
              <button key={k} onClick={() => setView(k)} style={{
                padding: '5px 12px', fontSize: 12, fontWeight: view === k ? 600 : 400,
                background: view === k ? 'var(--ink)' : 'var(--paper-2)',
                color: view === k ? 'var(--paper)' : 'var(--ink-3)',
                borderRight: idx < arr.length - 1 ? '1px solid var(--line)' : 'none', cursor: 'pointer',
              }}>{l}</button>
            ))}
          </div>
        </div>

        {/* Légende */}
        <div style={{ padding: '8px 20px', borderBottom: '1px solid var(--hairline)', flexShrink: 0 }}>
          <Legend activeKinds={activeKinds} onToggle={handleToggleKind} />
        </div>

        {/* Grille calendrier */}
        <div style={{ flex: 1, overflow: 'auto', display: 'flex', flexDirection: 'column' }}>
          {view === 'day' && (
            <WeekView days={[anchor]} events={visibleEvents} onNewAt={handleNewAt} onClickEvent={handleClickEvent} onMoveEvent={handleMoveEvent} dayStart={prefs.workDayStart} dayEnd={prefs.workDayEnd} collabColors={prefs.colorByCollaborator} />
          )}
          {view === 'week' && (
            <WeekView weekStart={weekStart} events={visibleEvents} onNewAt={handleNewAt} onClickEvent={handleClickEvent} onMoveEvent={handleMoveEvent} dayStart={prefs.workDayStart} dayEnd={prefs.workDayEnd} collabColors={prefs.colorByCollaborator} />
          )}
          {view === 'month' && (
            <MonthView monthStart={monthStart} events={visibleEvents} onNewAt={handleNewAt} onClickEvent={handleClickEvent} />
          )}
          {view === 'list' && (
            <ListView events={visibleEvents} onClickEvent={handleClickEvent} />
          )}
        </div>
        </React.Fragment>
        )}
      </div>

      {/* Sidebar — prochains RDV (masquée sur onglet Personnalisation) */}
      {tab === 'calendar' && (
      <div style={{ width: 240, borderLeft: '1px solid var(--line)', background: 'var(--paper-2)', display: 'flex', flexDirection: 'column', flexShrink: 0, overflow: 'hidden' }} key="sidebar">
        <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--hairline)' }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-2)' }}>Prochains RDV</div>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '8px 10px' }}>
          {upcomingAppts.length === 0 ? (
            <div style={{ fontSize: 12, color: 'var(--ink-4)', padding: '12px 4px' }}>Aucun RDV à venir</div>
          ) : upcomingAppts.map((ev, i) => {
            const meta = KIND_META[ev.kind] || KIND_META.autre;
            return (
              <button key={ev.id || i} onClick={() => handleClickEvent(ev)} style={{
                width: '100%', textAlign: 'left', padding: '9px 10px', marginBottom: 4,
                border: '1px solid var(--hairline)', borderRadius: 7,
                background: 'var(--paper)', cursor: 'pointer',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: ev.color || meta.color, flexShrink: 0 }} />
                  <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ev.title}</span>
                </div>
                <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
                  {new Date(ev.start).toLocaleDateString('fr-FR', { weekday: 'short', day: '2-digit', month: 'short' })} · {fmtHHMM(ev.start)}
                </div>
                {ev.clientName && <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2 }}>{ev.clientName}</div>}
              </button>
            );
          })}
        </div>
        <div style={{ padding: '10px 12px', borderTop: '1px solid var(--hairline)' }}>
          <div style={{ fontSize: 11, color: 'var(--ink-4)', textAlign: 'center' }}>
            {events.length} événement{events.length !== 1 ? 's' : ''} sur la période
          </div>
        </div>
      </div>
      )}

      {/* Modal */}
      {modal && (
        <AppointmentModal
          initial={modal.initial?.id ? modal.initial : { scheduledAt: modal.initial?.scheduledAt || new Date().toISOString() }}
          onSave={handleSave}
          onClose={() => setModal(null)}
        />
      )}
    </div>
  );
}

Object.assign(window, { CalendarPage });
