import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { api } from '../api.js';
import { useAuth } from '../context/AuthContext.jsx';
import NotifTypeAvatar, { TYPE_META } from '../components/NotifTypeAvatar.jsx';
import { fmtDate } from '../utils/format.js';

// ── Helpers ────────────────────────────────────────────────────────────────
function timeAgo(dateStr) {
  if (!dateStr) return '';
  const diff = (Date.now() - new Date(dateStr + 'Z').getTime()) / 1000;
  if (diff < 60)        return 'À l\'instant';
  if (diff < 3600)      return `Il y a ${Math.floor(diff / 60)} min`;
  if (diff < 86400)     return `Il y a ${Math.floor(diff / 3600)} h`;
  if (diff < 86400 * 7) return `Il y a ${Math.floor(diff / 86400)} j`;
  return new Date(dateStr + 'Z').toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' });
}

function fmtSize(bytes) {
  if (bytes < 1024) return `${bytes} o`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} Ko`;
  return `${(bytes / 1024 / 1024).toFixed(1)} Mo`;
}

function initials(name) {
  if (!name) return '?';
  // Si c'est un email, prendre les 2 premières lettres avant le @
  if (name.includes('@')) return name.split('@')[0].slice(0, 2).toUpperCase();
  return name.split(' ').filter(Boolean).map(w => w[0]).join('').toUpperCase().slice(0, 2);
}

// ── Avatar lettres ─────────────────────────────────────────────────────────
function UserAvatar({ name, isAdmin, size = 38 }) {
  const bg = isAdmin ? 'var(--primary)' : '#64748b';
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%', flexShrink: 0,
      background: bg, color: '#fff', fontSize: size * 0.36,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      fontWeight: 700, letterSpacing: '-0.5px',
    }}>
      {initials(name)}
    </div>
  );
}

// ── Badge statut ticket ────────────────────────────────────────────────────
function StatusBadge({ status }) {
  const styles = {
    open:     { bg: '#dcfce7', color: '#16a34a', label: 'Ouvert' },
    resolved: { bg: '#f1f5f9', color: '#64748b', label: 'Résolu' },
    pending:  { bg: '#fef9c3', color: '#a16207', label: 'En attente' },
  };
  const s = styles[status] ?? styles.open;
  return (
    <span style={{
      fontSize: 11, fontWeight: 600, padding: '2px 8px', borderRadius: 99,
      background: s.bg, color: s.color,
    }}>{s.label}</span>
  );
}

// ── Icône fichier selon MIME ───────────────────────────────────────────────
function FileIcon({ mime }) {
  if (mime?.startsWith('image/')) return '🖼';
  if (mime === 'application/pdf') return '📄';
  if (mime?.includes('zip') || mime?.includes('compressed')) return '🗜';
  if (mime?.includes('spreadsheet') || mime?.includes('excel')) return '📊';
  return '📎';
}

// ── Chip fichier avec aperçu image ────────────────────────────────────────
function FileChip({ file, onRemove }) {
  const isImage = file.type.startsWith('image/');
  const [url, setUrl] = useState(null);
  useEffect(() => {
    if (!isImage) return;
    const objUrl = URL.createObjectURL(file);
    setUrl(objUrl);
    return () => URL.revokeObjectURL(objUrl);
  }, [file, isImage]);
  return (
    <span className={`comm-file-chip${isImage ? ' has-preview' : ''}`}>
      {isImage && url
        ? <img src={url} alt={file.name} className="comm-file-chip-img" />
        : <span style={{ fontSize: 16 }}><FileIcon mime={file.type} /></span>
      }
      <span className="comm-attach-name">{file.name}</span>
      <button type="button" className="comm-file-remove" onClick={onRemove}>×</button>
    </span>
  );
}

// ── Gestion du coller image ───────────────────────────────────────────────
function onPasteImage(setter) {
  return (e) => {
    const items = Array.from(e.clipboardData?.items ?? []);
    const images = items
      .filter(item => item.type.startsWith('image/'))
      .map(item => item.getAsFile())
      .filter(Boolean)
      .map((f, i) => {
        const ext = f.type.split('/')[1]?.replace('jpeg', 'jpg') || 'png';
        return new File([f], `image-collée-${Date.now()}${i ? '-' + i : ''}.${ext}`, { type: f.type });
      });
    if (images.length) {
      setter(prev => [...prev, ...images]);
    }
  };
}

// ── Helper nom téléchargement ─────────────────────────────────────────────
function buildDownloadName(ticketNumber, msgDate, originalName) {
  const d    = msgDate ? new Date(msgDate.endsWith('Z') ? msgDate : msgDate + 'Z') : new Date();
  const yyyy = d.getFullYear();
  const mm   = String(d.getMonth() + 1).padStart(2, '0');
  const dd   = String(d.getDate()).padStart(2, '0');
  const hh   = String(d.getHours()).padStart(2, '0');
  const mn   = String(d.getMinutes()).padStart(2, '0');
  const ext  = (originalName.match(/\.[^.]+$/) ?? [''])[0];
  const base = originalName.replace(/\.[^.]+$/, '').replace(/[^\w\-]/g, '_').slice(0, 40);
  return `${ticketNumber}_${yyyy}-${mm}-${dd}_${hh}h${mn}_${base}${ext}`;
}

// ── Image jointe dans le thread ───────────────────────────────────────────
function AttachmentImage({ filename, originalName, size, onExpand, ticketNumber, msgDate }) {
  const [url, setUrl] = useState(null);
  useEffect(() => {
    let objUrl;
    api.blob(`/tickets/attachments/${filename}`)
    .then(blob => {
      objUrl = URL.createObjectURL(blob);
      setUrl(objUrl);
    }).catch(() => {});
    return () => { if (objUrl) URL.revokeObjectURL(objUrl); };
  }, [filename]);

  const handleDownload = () => {
    if (!url) return;
    const a = document.createElement('a');
    a.href = url; a.download = buildDownloadName(ticketNumber || 'TK', msgDate, originalName); a.click();
  };

  return (
    <div className="comm-attach-image">
      {url
        ? <img src={url} alt={originalName} className="comm-attach-img" />
        : <div className="comm-attach-img-skeleton">🖼</div>
      }
      {url && (
        <div className="comm-attach-image-overlay">
          <button className="comm-attach-overlay-btn" title="Agrandir" onClick={() => onExpand(url, originalName)}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
          </button>
          <button className="comm-attach-overlay-btn" title="Télécharger" onClick={handleDownload}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
          </button>
        </div>
      )}
      <div className="comm-attach-img-name">{originalName} · {fmtSize(size)}</div>
    </div>
  );
}

// ── Constantes tags tickets ───────────────────────────────────────────────
const TICKET_TYPES = [
  { value: 'bug_bloquant',     label: 'Bug bloquant' },
  { value: 'bug_non_bloquant', label: 'Bug non bloquant' },
  { value: 'amelioration',     label: 'Amélioration' },
  { value: 'question',         label: 'Question' },
];
const TICKET_PAGES = [
  { value: 'tableau_de_bord',  label: 'Tableau de bord' },
  { value: 'plateformes',      label: 'Plateformes' },
  { value: 'investissements',  label: 'Investissements' },
  { value: 'depots_retraits',  label: 'Dépôts / Retraits' },
  { value: 'fiscalite',        label: 'Fiscalité' },
  { value: 'mon_compte',       label: 'Mon compte' },
  { value: 'parametres',       label: 'Paramètres' },
  { value: 'autres',           label: 'Autres' },
];
const TYPE_COLORS = {
  bug_bloquant:     { background: '#fee2e2', color: '#b91c1c' },
  bug_non_bloquant: { background: '#ffedd5', color: '#c2410c' },
  amelioration:     { background: '#dbeafe', color: '#1d4ed8' },
  question:         { background: '#d1fae5', color: '#065f46' },
};

// ── TagSelect ─────────────────────────────────────────────────────────────
function TagSelect({ label, options, value, onChange, multi = false }) {
  const [open, setOpen] = useState(false);
  const [dropPos, setDropPos] = useState({ top: 0, left: 0, width: 0 });
  const triggerRef = useRef(null);
  const dropRef = useRef(null);

  useEffect(() => {
    if (!open) return;
    const h = e => {
      if (
        triggerRef.current && !triggerRef.current.contains(e.target) &&
        dropRef.current && !dropRef.current.contains(e.target)
      ) setOpen(false);
    };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);

  const openDropdown = () => {
    if (!open && triggerRef.current) {
      const r = triggerRef.current.getBoundingClientRect();
      setDropPos({ top: r.bottom + 4, left: r.left, width: r.width });
    }
    setOpen(o => !o);
  };

  const isSelected = v => multi ? (value || []).includes(v) : value === v;

  const toggle = v => {
    if (multi) {
      const arr = value || [];
      onChange(arr.includes(v) ? arr.filter(x => x !== v) : [...arr, v]);
    } else {
      onChange(value === v ? null : v);
      setOpen(false);
    }
  };

  const selectedCount = multi ? (value?.length || 0) : (value ? 1 : 0);
  const headerLabel = multi
    ? (selectedCount === 0 ? label : `${selectedCount} sélectionné${selectedCount > 1 ? 's' : ''}`)
    : (value ? options.find(o => o.value === value)?.label ?? label : label);

  return (
    <div className="tag-select" ref={triggerRef}>
      <button
        type="button"
        className={`tag-select-trigger${selectedCount > 0 ? ' has-value' : ''}${open ? ' open' : ''}`}
        onClick={openDropdown}
      >
        <span>{headerLabel}</span>
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
          {open ? <polyline points="18 15 12 9 6 15"/> : <polyline points="6 9 12 15 18 9"/>}
        </svg>
      </button>
      {open && createPortal(
        <div
          ref={dropRef}
          className="tag-select-dropdown"
          style={{
            position: 'fixed',
            top: dropPos.top,
            left: dropPos.left,
            minWidth: dropPos.width,
            zIndex: 9999,
          }}
        >
          {options.map(opt => {
            const sel = isSelected(opt.value);
            return (
              <div
                key={opt.value}
                className={`tag-select-option${sel ? ' selected' : ''}`}
                onClick={() => toggle(opt.value)}
              >
                {/* Bulle radio/checkbox custom */}
                <span style={{
                  flexShrink: 0,
                  width: 14, height: 14,
                  borderRadius: multi ? 3 : '50%',
                  border: `2px solid ${sel ? 'var(--primary)' : 'var(--border)'}`,
                  background: sel ? 'var(--primary)' : 'transparent',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  {sel && (
                    <svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3.5">
                      {multi
                        ? <polyline points="20 6 9 17 4 12"/>
                        : <circle cx="12" cy="12" r="4" fill="#fff" stroke="none"/>
                      }
                    </svg>
                  )}
                </span>
                <span>{opt.label}</span>
              </div>
            );
          })}
        </div>,
        document.body
      )}
    </div>
  );
}

// ── TicketChips ───────────────────────────────────────────────────────────
function TicketChips({ ticketType, ticketPages }) {
  const type  = TICKET_TYPES.find(t => t.value === ticketType);
  const pages = ticketPages ? (Array.isArray(ticketPages) ? ticketPages : JSON.parse(ticketPages)) : [];
  if (!type && pages.length === 0) return null;
  return (
    <div className="ticket-chips">
      {type && (
        <span className="ticket-chip ticket-chip-type" style={TYPE_COLORS[ticketType] || {}}>
          {type.label}
        </span>
      )}
      {pages.map(p => {
        const page = TICKET_PAGES.find(x => x.value === p);
        return page ? <span key={p} className="ticket-chip ticket-chip-page">{page.label}</span> : null;
      })}
    </div>
  );
}

// ── MessageBody — rendu HTML avec overlay CSS sur images inline ────────────
const ICON_EXPAND_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>`;
const ICON_DOWNLOAD_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;

function MessageBody({ html, onExpand, ticketNumber, msgDate }) {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    el.querySelectorAll('.msg-inline-img:not([data-hovered])').forEach(img => {
      img.setAttribute('data-hovered', '1');
      const wrap = document.createElement('span');
      wrap.className = 'msg-img-wrap';
      img.parentNode.insertBefore(wrap, img);
      wrap.appendChild(img);
      const overlay = document.createElement('span');
      overlay.className = 'msg-img-hover-overlay';
      overlay.innerHTML =
        `<button type="button" class="comm-attach-overlay-btn" title="Agrandir" data-img-action="expand">${ICON_EXPAND_SVG}</button>` +
        `<button type="button" class="comm-attach-overlay-btn" title="Télécharger" data-img-action="download">${ICON_DOWNLOAD_SVG}</button>`;
      wrap.appendChild(overlay);
    });
  }, [html]);

  const handleClick = useCallback((e) => {
    const btn = e.target.closest('[data-img-action]');
    if (btn) {
      const img = btn.closest('.msg-img-wrap')?.querySelector('img');
      if (!img) return;
      if (btn.dataset.imgAction === 'expand') {
        onExpand(img.src, img.alt || 'image');
      } else {
        const a = document.createElement('a');
        a.href = img.src; a.download = buildDownloadName(ticketNumber || 'TK', msgDate, img.alt || 'image.png'); a.click();
      }
      return;
    }
    if (e.target.tagName === 'IMG') onExpand(e.target.src, e.target.alt || 'image');
  }, [onExpand]);

  return (
    <div
      ref={ref}
      className="comm-message-body"
      dangerouslySetInnerHTML={{ __html: sanitizeHTML(html) }}
      onClick={handleClick}
    />
  );
}

// ── Sanitisation HTML (affichage messages) ────────────────────────────────
const ALLOWED_TAGS = new Set(['b','i','u','s','strong','em','ul','ol','li','a','br','p','div','span','img']);
function sanitizeHTML(html) {
  if (!html) return '';
  const doc = new DOMParser().parseFromString(html, 'text/html');
  function clean(node) {
    for (const child of [...node.childNodes]) {
      if (child.nodeType === 3) continue;
      if (child.nodeType === 1) {
        const tag = child.tagName.toLowerCase();
        if (!ALLOWED_TAGS.has(tag)) { child.replaceWith(...child.childNodes); continue; }
        if (tag === 'img') {
          const src = child.getAttribute('src') ?? '';
          if (!src.startsWith('data:image/')) { child.remove(); continue; }
          // Garde uniquement src et alt
          for (const attr of [...child.attributes]) {
            if (attr.name !== 'src' && attr.name !== 'alt') child.removeAttribute(attr.name);
          }
          child.setAttribute('class', 'msg-inline-img');
          continue; // pas d'enfants
        }
        for (const attr of [...child.attributes]) {
          if (tag === 'a' && attr.name === 'href') continue;
          child.removeAttribute(attr.name);
        }
        if (tag === 'a') { child.setAttribute('target','_blank'); child.setAttribute('rel','noreferrer'); }
        clean(child);
      } else { child.remove(); }
    }
  }
  clean(doc.body);
  return doc.body.innerHTML;
}

// ── Nettoie l'HTML de l'éditeur avant envoi (retire les wrappers rte-img-wrap) ──
function stripPreview(html) {
  if (!html) return '';
  return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120);
}

function stripEditorHTML(html) {
  if (!html) return '';
  const div = document.createElement('div');
  div.innerHTML = html;
  for (const wrap of [...div.querySelectorAll('.rte-img-wrap')]) {
    const img = wrap.querySelector('img');
    if (img) wrap.replaceWith(img); else wrap.remove();
  }
  return div.innerHTML;
}

// ── Éditeur de texte riche (contenteditable) ───────────────────────────────
function RichTextEditor({ value, onChange, onImgExpand, placeholder, minHeight = 100 }) {
  const ref = useRef(null);

  useEffect(() => {
    if (ref.current && (value === '' || value === null || value === undefined)) {
      ref.current.innerHTML = '';
    }
  }, [value]);

  const exec = (cmd, val = null) => {
    // Ne redonner le focus que si l'éditeur ne l'a pas déjà
    // (focus() détruirait la sélection active)
    if (document.activeElement !== ref.current) ref.current?.focus();
    document.execCommand(cmd, false, val);
    onChange(ref.current?.innerHTML ?? '');
  };

  const insertLink = () => {
    const url = window.prompt('URL du lien :');
    if (url?.trim()) exec('createLink', url.trim());
  };

  // Coller une image → insertion inline au curseur avec overlay Agrandir/Supprimer
  const handlePaste = (e) => {
    const items = Array.from(e.clipboardData?.items ?? []);
    const imageItems = items.filter(item => item.type.startsWith('image/'));
    if (!imageItems.length) return;
    e.preventDefault();
    imageItems.forEach(item => {
      const file = item.getAsFile();
      if (!file) return;
      const reader = new FileReader();
      reader.onload = ev => {
        const dataUrl = ev.target.result;
        const ICON_EXPAND = `<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'><polyline points='15 3 21 3 21 9'/><polyline points='9 21 3 21 3 15'/><line x1='21' y1='3' x2='14' y2='10'/><line x1='3' y1='21' x2='10' y2='14'/></svg>`;
        const ICON_DEL = `<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'><polyline points='3 6 5 6 21 6'/><path d='M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6'/><path d='M10 11v6'/><path d='M14 11v6'/><path d='M9 6V4h6v2'/></svg>`;
        const html = `<span class="rte-img-wrap" contenteditable="false">`
          + `<img src="${dataUrl}" class="rte-inline-img" alt="image collée" />`
          + `<span class="rte-img-overlay">`
          + `<button type="button" class="rte-img-overlay-btn" data-rte-action="expand">${ICON_EXPAND} Agrandir</button>`
          + `<button type="button" class="rte-img-overlay-btn rte-img-del" data-rte-action="remove">${ICON_DEL} Supprimer</button>`
          + `</span></span>​`;
        if (document.activeElement !== ref.current) ref.current?.focus();
        document.execCommand('insertHTML', false, html);
        onChange(ref.current?.innerHTML ?? '');
      };
      reader.readAsDataURL(file);
    });
  };

  // Délégation de clics sur les boutons Agrandir/Supprimer dans l'éditeur
  const handleEditorClick = (e) => {
    const btn = e.target.closest('[data-rte-action]');
    if (!btn) return;
    e.preventDefault();
    const action = btn.dataset.rteAction;
    const wrap = btn.closest('.rte-img-wrap');
    if (action === 'remove' && wrap) {
      wrap.remove();
      onChange(ref.current?.innerHTML ?? '');
    } else if (action === 'expand' && wrap) {
      const img = wrap.querySelector('img');
      if (img) onImgExpand?.(img.src, 'image collée');
    }
  };

  return (
    <div className="rte-wrap">
      <div className="rte-toolbar">
        <button type="button" className="rte-btn" title="Gras (Ctrl+B)"
          onMouseDown={e => { e.preventDefault(); exec('bold'); }}><b>B</b></button>
        <button type="button" className="rte-btn" title="Italique (Ctrl+I)"
          onMouseDown={e => { e.preventDefault(); exec('italic'); }}><i>I</i></button>
        <button type="button" className="rte-btn" title="Souligné (Ctrl+U)"
          onMouseDown={e => { e.preventDefault(); exec('underline'); }}><u>U</u></button>
        <button type="button" className="rte-btn" title="Barré"
          onMouseDown={e => { e.preventDefault(); exec('strikeThrough'); }}><s>S</s></button>
        <div className="rte-sep" />
        <button type="button" className="rte-btn" title="Lien"
          onMouseDown={e => { e.preventDefault(); insertLink(); }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
          Lien
        </button>
        <div className="rte-sep" />
        <button type="button" className="rte-btn" title="Liste à puces"
          onMouseDown={e => { e.preventDefault(); exec('insertUnorderedList'); }}>
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="9" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="9" y1="18" x2="21" y2="18"/><circle cx="3.5" cy="6" r="1"/><circle cx="3.5" cy="12" r="1"/><circle cx="3.5" cy="18" r="1"/></svg>
        </button>
        <button type="button" className="rte-btn" title="Liste numérotée"
          onMouseDown={e => { e.preventDefault(); exec('insertOrderedList'); }}>
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-2-2-2"/></svg>
        </button>
      </div>
      <div
        ref={ref}
        className="rte-editor"
        contentEditable
        suppressContentEditableWarning
        onInput={e => onChange(e.currentTarget.innerHTML)}
        onPaste={handlePaste}
        onClick={handleEditorClick}
        data-placeholder={placeholder}
        style={{ minHeight }}
      />
    </div>
  );
}

// ── Sélecteur de type de notification ─────────────────────────────────────
// ── TypeDropdown — liste déroulante custom avec icônes ─────────────────────
function TypeDropdown({ value, onChange }) {
  const [open, setOpen] = useState(false);
  const [pos, setPos] = useState({ top: 0, left: 0, width: 0 });
  const triggerRef = useRef(null);
  const dropRef = useRef(null);
  const meta = TYPE_META[value] ?? TYPE_META.info;

  useEffect(() => {
    if (!open) return;
    const h = e => {
      if (triggerRef.current?.contains(e.target) || dropRef.current?.contains(e.target)) return;
      setOpen(false);
    };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);

  const openDrop = () => {
    if (triggerRef.current) {
      const r = triggerRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 4, left: r.left, width: r.width });
    }
    setOpen(o => !o);
  };

  return (
    <div ref={triggerRef} style={{ position: 'relative' }}>
      <button
        type="button"
        onClick={openDrop}
        style={{
          display: 'flex', alignItems: 'center', gap: 10, width: '100%',
          padding: '8px 12px', borderRadius: 8, cursor: 'pointer', fontSize: 13,
          border: `2px solid ${meta.color}`, background: meta.bg, color: meta.color,
          fontWeight: 600,
        }}
      >
        <NotifTypeAvatar type={value} size={20} />
        <span style={{ flex: 1, textAlign: 'left' }}>{meta.label}</span>
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
          {open ? <polyline points="18 15 12 9 6 15"/> : <polyline points="6 9 12 15 18 9"/>}
        </svg>
      </button>
      {open && createPortal(
        <div
          ref={dropRef}
          style={{
            position: 'fixed', top: pos.top, left: pos.left, width: pos.width,
            zIndex: 9999, background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.15)', padding: '4px 0',
          }}
        >
          {Object.entries(TYPE_META).map(([k, m]) => (
            <div
              key={k}
              onClick={() => { onChange(k); setOpen(false); }}
              style={{
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '8px 12px', cursor: 'pointer', fontSize: 13,
                color: k === value ? m.color : 'var(--text)',
                background: k === value ? m.bg : 'transparent',
                fontWeight: k === value ? 600 : 400,
              }}
              onMouseEnter={e => { if (k !== value) e.currentTarget.style.background = 'var(--surface-2)'; }}
              onMouseLeave={e => { if (k !== value) e.currentTarget.style.background = 'transparent'; }}
            >
              <span style={{
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                width: 28, height: 28, borderRadius: '50%', background: m.bg, color: m.color, flexShrink: 0,
              }}>
                <NotifTypeAvatar type={k} size={16} />
              </span>
              <span>{m.label}</span>
              {k === value && (
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ marginLeft: 'auto', color: m.color }}>
                  <polyline points="20 6 9 17 4 12"/>
                </svg>
              )}
            </div>
          ))}
        </div>,
        document.body
      )}
    </div>
  );
}

function TypeSelector({ value, onChange }) {
  return (
    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
      {Object.entries(TYPE_META).map(([k, m]) => (
        <button
          key={k}
          type="button"
          onClick={() => onChange(k)}
          style={{
            display: 'flex', alignItems: 'center', gap: 6,
            padding: '5px 10px', borderRadius: 8, cursor: 'pointer', fontSize: 12,
            border: value === k ? `2px solid ${m.color}` : '2px solid var(--border)',
            background: value === k ? m.bg : 'transparent',
            color: value === k ? m.color : 'var(--text-muted)',
            fontWeight: value === k ? 600 : 400,
          }}
        >
          <NotifTypeAvatar type={k} size={22} />
          {m.label}
        </button>
      ))}
    </div>
  );
}

// ── RecipientDropdown — destinataire notification broadcast ─────────────────
function RecipientDropdown({ value, onChange, users }) {
  const [open, setOpen] = useState(false);
  const [search, setSearch] = useState('');
  const [pos, setPos]   = useState({ top: 0, left: 0, width: 0 });
  const triggerRef = useRef(null);
  const dropRef    = useRef(null);
  const searchRef  = useRef(null);

  useEffect(() => {
    if (!open) return;
    setTimeout(() => searchRef.current?.focus(), 30);
    const h = e => {
      if (triggerRef.current?.contains(e.target) || dropRef.current?.contains(e.target)) return;
      setOpen(false);
    };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);

  const openDrop = () => {
    if (triggerRef.current) {
      const r = triggerRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 4, left: r.left, width: r.width });
    }
    setSearch('');
    setOpen(o => !o);
  };

  const select = (v) => { onChange(v); setOpen(false); };

  // Label affiché dans le bouton
  const SPECIAL = [
    { value: '', label: 'Tous les utilisateurs', icon: (
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="7" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4"/><circle cx="17" cy="9" r="4" opacity=".5"/><path d="M21 21v-2a4 4 0 0 0-4-4h-1" opacity=".5"/></svg>
    )},
    { value: 'admins', label: 'Tous les administrateurs', icon: (
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2l3 3h4v4l3 3-3 3v4h-4l-3 3-3-3H5v-4L2 12l3-3V5h4z"/></svg>
    )},
  ];

  const specialMatch = SPECIAL.find(s => s.value === value);
  const userMatch = users.find(u => String(u.id) === String(value));
  const triggerLabel = specialMatch?.label ?? userMatch?.display_name ?? userMatch?.email ?? 'Tous les utilisateurs';
  const triggerIcon  = specialMatch?.icon ?? null;

  // Filtrer users par recherche
  const q = search.toLowerCase();
  const filteredUsers = q
    ? users.filter(u => (u.display_name ?? '').toLowerCase().includes(q) || u.email.toLowerCase().includes(q))
    : users;

  return (
    <div ref={triggerRef}>
      <button
        type="button"
        onClick={openDrop}
        style={{
          display: 'flex', alignItems: 'center', gap: 8, width: '100%',
          padding: '8px 12px', borderRadius: 8, cursor: 'pointer', fontSize: 13,
          border: '1px solid var(--border)', background: 'var(--bg-input, var(--surface-2))',
          color: 'var(--text)',
        }}
      >
        {triggerIcon && <span style={{ color: 'var(--text-muted)', flexShrink: 0 }}>{triggerIcon}</span>}
        <span style={{ flex: 1, textAlign: 'left' }}>{triggerLabel}</span>
        <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
          {open ? <polyline points="18 15 12 9 6 15"/> : <polyline points="6 9 12 15 18 9"/>}
        </svg>
      </button>
      {open && createPortal(
        <div
          ref={dropRef}
          style={{
            position: 'fixed', top: pos.top, left: pos.left, minWidth: pos.width,
            zIndex: 9999, background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.15)', overflow: 'hidden',
          }}
        >
          {/* Champ recherche */}
          <div style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
            <input
              ref={searchRef}
              value={search}
              onChange={e => setSearch(e.target.value)}
              placeholder="Rechercher..."
              style={{
                width: '100%', padding: '5px 8px', borderRadius: 6, fontSize: 12,
                border: '1px solid var(--border)', background: 'var(--surface-2)',
                color: 'var(--text)', outline: 'none', boxSizing: 'border-box',
              }}
            />
          </div>
          {/* Options spéciales (pas filtrées) */}
          <div style={{ maxHeight: 240, overflowY: 'auto' }}>
            {SPECIAL.map(s => (
              <div
                key={s.value}
                onClick={() => select(s.value)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 8,
                  padding: '8px 12px', cursor: 'pointer', fontSize: 13,
                  background: value === s.value ? 'var(--primary-light, rgba(99,102,241,0.1))' : 'transparent',
                  color: value === s.value ? 'var(--primary)' : 'var(--text)',
                  fontWeight: value === s.value ? 600 : 400,
                }}
                onMouseEnter={e => { if (value !== s.value) e.currentTarget.style.background = 'var(--surface-2)'; }}
                onMouseLeave={e => { if (value !== s.value) e.currentTarget.style.background = 'transparent'; }}
              >
                <span style={{ color: value === s.value ? 'var(--primary)' : 'var(--text-muted)', flexShrink: 0 }}>{s.icon}</span>
                <span style={{ flex: 1 }}>{s.label}</span>
                {value === s.value && (
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ color: 'var(--primary)' }}>
                    <polyline points="20 6 9 17 4 12"/>
                  </svg>
                )}
              </div>
            ))}
            {/* Séparateur */}
            {filteredUsers.length > 0 && (
              <div style={{ padding: '4px 12px 2px', fontSize: 10, fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.06em', borderTop: '1px solid var(--border)', marginTop: 2 }}>
                Utilisateurs individuels
              </div>
            )}
            {filteredUsers.map(u => {
              const sel = String(u.id) === String(value);
              const name = u.display_name ?? u.email;
              return (
                <div
                  key={u.id}
                  onClick={() => select(String(u.id))}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8,
                    padding: '7px 12px', cursor: 'pointer', fontSize: 13,
                    background: sel ? 'var(--primary-light, rgba(99,102,241,0.1))' : 'transparent',
                    color: sel ? 'var(--primary)' : 'var(--text)',
                    fontWeight: sel ? 600 : 400,
                  }}
                  onMouseEnter={e => { if (!sel) e.currentTarget.style.background = 'var(--surface-2)'; }}
                  onMouseLeave={e => { if (!sel) e.currentTarget.style.background = 'transparent'; }}
                >
                  <span style={{
                    width: 26, height: 26, borderRadius: '50%', background: 'var(--primary)', color: '#fff',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 700, flexShrink: 0,
                  }}>
                    {name.charAt(0).toUpperCase()}
                  </span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: sel ? 600 : 400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</div>
                    {u.display_name && <div style={{ fontSize: 11, color: 'var(--text-muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{u.email}</div>}
                  </div>
                  {sel && (
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ color: 'var(--primary)' }}>
                      <polyline points="20 6 9 17 4 12"/>
                    </svg>
                  )}
                </div>
              );
            })}
            {filteredUsers.length === 0 && q && (
              <div style={{ padding: '10px 12px', fontSize: 12, color: 'var(--text-muted)', textAlign: 'center' }}>Aucun résultat</div>
            )}
          </div>
        </div>,
        document.body
      )}
    </div>
  );
}

// ══════════════════════════════════════════════════════════════════════════
// COMPOSANT PRINCIPAL
// ══════════════════════════════════════════════════════════════════════════
export default function Communication() {
  const { user, isAdmin }           = useAuth();
  const [searchParams, setSearchParams] = useSearchParams();
  const navigate                    = useNavigate();
  const [notifMenuPos, setNotifMenuPos]     = useState(null); // { x, y }
  const [supportMenuPos, setSupportMenuPos] = useState(null); // { x, y }
  const [showAssignModal, setShowAssignModal]   = useState(false);
  const [showCategModal, setShowCategModal]     = useState(false);
  const [categType, setCategType]               = useState(null);
  const [categPages, setCategPages]             = useState([]);
  const [assignSearch, setAssignSearch]       = useState('');

  const [tab, setTab]               = useState('support'); // 'support' | 'notifications'
  const [showBroadcastForm, setShowBroadcastForm] = useState(false);
  const [lightbox, setLightbox] = useState(null); // { url, name }
  const [editingMsg, setEditingMsg] = useState(null); // { id, body }
  const [editSaving, setEditSaving] = useState(false);
  // Tick toutes les 10s pour rafraîchir les boutons "Modifier" (fenêtre 5 min)
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 10_000);
    return () => clearInterval(id);
  }, []);
  const [tickets, setTickets]       = useState([]);
  const [ticketSearch, setTicketSearch]   = useState('');
  const [ticketFilter, setTicketFilter]     = useState(new Set(['open', 'pending'])); // multi-sélection
  const [notifFilter, setNotifFilter]       = useState(new Set(['unread'])); // multi-sélection
  const [selectedTicket, setSelectedTicket] = useState(null);
  const [thread, setThread]         = useState(null); // { ticket, messages }
  const [notifs, setNotifs]         = useState([]);
  const [notifPage, setNotifPage]    = useState(0);
  const [notifsTotal, setNotifsTotal] = useState(0);
  const [selectedNotif, setSelectedNotif] = useState(null);
  const [loading, setLoading]       = useState(false);

  // ── Nouveau ticket ───────────────────────────────────────────────────
  const [showCompose, setShowCompose] = useState(false);
  const [composeSubject, setComposeSubject] = useState('');
  const [composeBody, setComposeBody] = useState('');
  const [composeFiles, setComposeFiles] = useState([]);
  const [composeType, setComposeType]   = useState(null);
  const [composePages, setComposePages] = useState([]);
  const [composeSending, setComposeSending] = useState('');
  const fileInputRef = useRef(null);

  // ── Répondre ─────────────────────────────────────────────────────────
  const [replyBody, setReplyBody]   = useState('');
  const [replyFiles, setReplyFiles] = useState([]);
  const replyFileRef                = useRef(null);
  const [replySending, setReplySending] = useState(false);

  // ── Broadcast (admin) ────────────────────────────────────────────────
  const [bcType, setBcType]         = useState('announcement');
  const [bcTitle, setBcTitle]       = useState('');
  const [bcBody, setBcBody]         = useState('');
  const [bcUserId, setBcUserId]     = useState('');
  const [bcSending, setBcSending]   = useState(false);
  const [bcResult, setBcResult]     = useState(null);
  const [users, setUsers]           = useState([]);

  // ── Charger tickets ──────────────────────────────────────────────────
  const fetchTickets = useCallback(async () => {
    setLoading(true);
    try {
      const data = await api.get('/tickets');
      setTickets(data.tickets ?? []);
    } catch { /* silencieux */ }
    setLoading(false);
  }, []);

  // ── Charger notifications ────────────────────────────────────────────
  const fetchNotifs = useCallback(async () => {
    try {
      const data = await api.get('/notifications', { limit: 50 });
      const list = data.notifications ?? [];
      setNotifs(list);
      setNotifsTotal(data.total ?? 0);
      setNotifPage(0);
    } catch { /* silencieux */ }
  }, []);

  // ── Charger thread ───────────────────────────────────────────────────
  const fetchThread = useCallback(async (id) => {
    try {
      const data = await api.get(`/tickets/${id}`);
      setThread(data);
    } catch { /* silencieux */ }
  }, []);

  // ── Charger users (admin) ────────────────────────────────────────────
  const fetchUsers = useCallback(async () => {
    if (!isAdmin) return;
    try {
      const data = await api.get('/admin/users');
      setUsers((data.users ?? data ?? []).filter(u => u.role === 'admin'));
    } catch { /* silencieux */ }
  }, [isAdmin]);

  useEffect(() => {
    fetchTickets();
    fetchNotifs();
    if (isAdmin) fetchUsers();
  }, [fetchTickets, fetchNotifs, fetchUsers]);

  // ── Restaurer ticket depuis URL ──────────────────────────────────────
  useEffect(() => {
    const ticketId = searchParams.get('ticket');
    if (ticketId) {
      setTab('support');
      setSelectedTicket(Number(ticketId));
      fetchThread(ticketId);
      // Nettoyer le param URL sans recharger
      setSearchParams({}, { replace: true });
    }
  }, [searchParams]); // eslint-disable-line

  // ── Ouvrir un ticket ─────────────────────────────────────────────────
  const openTicket = (id) => {
    setSelectedTicket(id);
    setSelectedNotif(null);
    fetchThread(id);
    setSearchParams({ ticket: id });
    setReplyBody('');
    setReplyFiles([]);
  };

  // ── Ouvrir une notification ──────────────────────────────────────────
  const openNotif = async (n) => {
    setSelectedNotif(n);
    setSelectedTicket(null);
    setShowBroadcastForm(false);
    if (!n.read) {
      try {
        await api.patch(`/notifications/${n.id}/read`);
        setNotifs(prev => prev.map(x => x.id === n.id ? { ...x, read: 1 } : x));
        window.dispatchEvent(new CustomEvent('notif:refresh'));
      } catch { /* silencieux */ }
    }
  };

  // ── Créer ticket ─────────────────────────────────────────────────────
  const submitTicket = async (e) => {
    e.preventDefault();
    if (!composeSubject.trim() || !composeBody.replace(/<[^>]*>/g,'').trim()) return;
    setComposeSending('sending');
    try {
      const fd = new FormData();
      fd.append('subject', composeSubject.trim());
      fd.append('body', stripEditorHTML(composeBody).trim());
      if (composeType) fd.append('ticket_type', composeType);
      if (composePages.length) fd.append('ticket_pages', JSON.stringify(composePages));
      for (const f of composeFiles) fd.append('attachments', f);
      const data = await api.postForm('/tickets', fd);
      setShowCompose(false);
      setComposeSubject('');
      setComposeBody('');
      setComposeType(null);
      setComposePages([]);
      setComposeFiles([]);
      await fetchTickets();
      if (data.ticketId) openTicket(data.ticketId);
    } catch { /* silencieux */ }
    setComposeSending('');
  };

  // ── Répondre ─────────────────────────────────────────────────────────
  const submitReply = async (e) => {
    e.preventDefault();
    if (!replyBody.trim() || !thread) return;
    setReplySending(true);
    try {
      const fd = new FormData();
      fd.append('body', stripEditorHTML(replyBody).trim());
      for (const f of replyFiles) fd.append('attachments', f);
      await api.postForm(`/tickets/${thread.ticket.id}/messages`, fd);
      setReplyBody('');
      setReplyFiles([]);
      await fetchThread(thread.ticket.id);
      await fetchTickets();
    } catch { /* silencieux */ }
    setReplySending(false);
  };

  // ── Résoudre / rouvrir ───────────────────────────────────────────────
  const toggleStatus = async () => {
    if (!thread) return;
    const newStatus = thread.ticket.status === 'open' ? 'resolved' : 'open';
    try {
      await api.patch(`/tickets/${thread.ticket.id}/status`, { status: newStatus });
      await fetchThread(thread.ticket.id);
      await fetchTickets();
    } catch { /* silencieux */ }
  };

  // ── Assigner un ticket ───────────────────────────────────────────────
  const assignTicket = async (adminId) => {
    if (!thread) return;
    try {
      await api.patch(`/tickets/${thread.ticket.id}/assign`, { assigned_to: adminId });
      await fetchThread(thread.ticket.id);
    } catch { /* silencieux */ }
  };

  // ── Mettre en attente ────────────────────────────────────────────────
  const setTicketPending = async () => {
    if (!thread) return;
    try {
      await api.patch(`/tickets/${thread.ticket.id}/status`, { status: 'pending' });
      await fetchThread(thread.ticket.id);
      await fetchTickets();
    } catch { /* silencieux */ }
  };

  // ── Broadcast ────────────────────────────────────────────────────────
  const submitBroadcast = async (e) => {
    e.preventDefault();
    if (!bcTitle.trim()) return;
    setBcSending(true);
    setBcResult(null);
    try {
      const payload = { type: bcType, title: bcTitle.trim(), body: bcBody.trim() || undefined };
      if (bcUserId) payload.user_id = bcUserId === 'admins' ? 'admins' : Number(bcUserId);
      const data = await api.post('/notifications/broadcast', payload);
      setBcResult({ ok: true, msg: `Envoyé à ${data.sent} utilisateur${data.sent > 1 ? 's' : ''}` });
      setBcTitle('');
      setBcBody('');
      setBcUserId('');
      window.dispatchEvent(new CustomEvent('notif:refresh'));
      await fetchNotifs(); // rafraîchir la liste locale immédiatement
      setShowBroadcastForm(false);
    } catch {
      setBcResult({ ok: false, msg: 'Erreur lors de l\'envoi' });
    }
    setBcSending(false);
  };

  // ── Supprimer toutes les notifications ────────────────────────────────────
  const deleteAllNotifs = async () => {
    try {
      await Promise.all(notifs.map(n => api.del(`/notifications/${n.id}`)));
      setNotifs([]);
      setSelectedNotif(null);
      window.dispatchEvent(new CustomEvent('notif:refresh'));
    } catch { /* silencieux */ }
  };

  // ── Supprimer une notification ────────────────────────────────────────────
  const deleteNotif = async (id) => {
    try {
      await api.del(`/notifications/${id}`);
      setNotifs(prev => prev.filter(n => n.id !== id));
      setSelectedNotif(null);
      window.dispatchEvent(new CustomEvent('notif:refresh'));
    } catch { /* silencieux */ }
  };

  // ── Marquer toutes notifs lues ───────────────────────────────────────
  const markAllRead = async () => {
    try {
      await api.patch('/notifications/read-all');
      setNotifs(prev => prev.map(n => ({ ...n, read: 1 })));
      window.dispatchEvent(new CustomEvent('notif:refresh'));
    } catch { /* silencieux */ }
  };

  // ── Édition message (fenêtre 5 min) ────────────────────────────────────
  const EDIT_WINDOW_MS = 5 * 60 * 1000;
  const canEditMsg = (msg) =>
    msg.user_id === user?.id &&
    (now - new Date(msg.created_at + 'Z').getTime()) < EDIT_WINDOW_MS;

  const fmtRemaining = (msg) => {
    const ms = EDIT_WINDOW_MS - (now - new Date(msg.created_at + 'Z').getTime());
    if (ms <= 0) return null;
    const mins = Math.ceil(ms / 60_000);
    return mins <= 1 ? '< 1 min' : `${mins} min`;
  };

  const saveEdit = async (msg) => {
    if (!editingMsg?.body?.replace(/<[^>]*>/g, '').trim()) return;
    setEditSaving(true);
    try {
      await api.put(`/tickets/${msg.ticket_id}/messages/${msg.id}`, { body: editingMsg.body });
      // Mettre à jour localement sans recharger tout le thread
      setThread(prev => ({
        ...prev,
        messages: prev.messages.map(m =>
          m.id === msg.id ? { ...m, body: editingMsg.body, updated_at: new Date().toISOString() } : m
        ),
      }));
      setEditingMsg(null);
    } catch (e) {
      alert(e.message || 'Erreur lors de la sauvegarde');
    }
    setEditSaving(false);
  };

  const unreadCount = notifs.filter(n => !n.read).length;

  // ────────────────────────────────────────────────────────────────────
  // RENDER
  // ────────────────────────────────────────────────────────────────────
  return (
    <>
      <div className="topbar" style={{ display: 'none' }} aria-hidden />
      <div className="comm-page">
        {/* ── Topbar 3 colonnes ── */}
        <div className="comm-topbar">
          {/* Colonne 1 — Titre page */}
          <div className="comm-topbar-1">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ flexShrink: 0 }}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
            <span>Communication</span>
          </div>
          {/* Colonne 2 — Dossier + filtres */}
          <div className="comm-topbar-2">
            <span className="comm-topbar-folder" style={{ fontSize: 15, fontWeight: 700 }}>{tab === 'support' ? 'Support' : 'Notifications'}</span>
          </div>
          {/* Colonne 3 — Toolbar contextuelle */}
          <div className="comm-topbar-3">
            {tab === 'support' && thread && (
              <>
                <div style={{ display: 'flex', gap: 6, marginLeft: 'auto', flexShrink: 0, alignItems: 'center' }}>
                  <button
                    className="btn-icon-sm"
                    onClick={e => {
                      const r = e.currentTarget.getBoundingClientRect();
                      setSupportMenuPos({ x: r.right, y: r.bottom + 4 });
                    }}
                  >
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
                  </button>
                </div>
              </>
            )}
            {tab === 'notifications' && selectedNotif && (
              <div style={{ display: 'flex', gap: 6, marginLeft: 'auto', alignItems: 'center' }}>
                <button
                  className="btn-icon-sm"
                  onClick={e => {
                    const r = e.currentTarget.getBoundingClientRect();
                    setNotifMenuPos({ x: r.right, y: r.bottom + 4 });
                  }}
                >
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
                </button>
              </div>
            )}
          </div>
        </div>
        {/* ── Corps 3 panneaux ── */}
        <div className="comm-wrap">

        {/* ── Sidebar gauche ── */}
        <aside className="comm-sidebar">
          {tab === 'support' ? (
            <button
              className="comm-compose-btn"
              onClick={() => setShowCompose(true)}
            >
              + Nouveau ticket
            </button>
          ) : isAdmin ? (
            <button
              className="comm-compose-btn"
              onClick={() => { setShowBroadcastForm(true); setSelectedNotif(null); }}
            >
              + Nouvelle notification
            </button>
          ) : null}

          <nav className="comm-nav">
            <button
              className={`comm-nav-item${tab === 'support' ? ' active' : ''}`}
              onClick={() => setTab('support')}
            >
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
              </svg>
              Support
              {tickets.filter(t => t.status === 'open').length > 0 && (
                <span className="comm-nav-badge">
                  {tickets.filter(t => t.status === 'open').length}
                </span>
              )}
            </button>
            <button
              className={`comm-nav-item${tab === 'notifications' ? ' active' : ''}`}
              onClick={() => {
                setTab('notifications');
              }}
            >
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
                <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
              </svg>
              Notifications
              {unreadCount > 0 && <span className="comm-nav-badge">{unreadCount}</span>}
            </button>
          </nav>

          {/* Filtres statut tickets (sidebar) */}
          {tab === 'support' && (
            <div className="comm-sidebar-section">
              <div className="comm-sidebar-label">Tickets ouverts : {tickets.filter(t => t.status === 'open').length}</div>
              <div className="comm-sidebar-label">Tickets résolus : {tickets.filter(t => t.status === 'resolved').length}</div>
            </div>
          )}
        </aside>

        {/* ── Liste centrale ── */}
        <div className="comm-list">
          {tab === 'support' && (
            <>
              <div className="comm-list-header" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 8 }}>
                <div className="comm-topbar-filters">
                  {[['open','Ouverts'],['pending','En attente'],['resolved','Résolus']].map(([v,l]) => {
                    const active = ticketFilter.has(v);
                    return (
                      <button
                        key={v}
                        className="comm-topbar-filter-btn"
                        style={active ? {
                          background: 'var(--primary)',
                          color: '#fff',
                          borderColor: 'var(--primary)',
                          display: 'flex', alignItems: 'center', gap: 5,
                        } : {}}
                        onClick={() => setTicketFilter(prev => {
                          const next = new Set(prev);
                          if (next.has(v)) { next.delete(v); } else { next.add(v); }
                          return next;
                        })}
                      >
                        {active && (
                          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" style={{ flexShrink: 0 }}>
                            <polyline points="20 6 9 17 4 12"/>
                          </svg>
                        )}
                        {l}
                      </button>
                    );
                  })}
                </div>
                <input
                  className="comm-search"
                  placeholder="Rechercher un ticket…"
                  value={ticketSearch}
                  onChange={e => setTicketSearch(e.target.value)}
                />
              </div>
              <div className="comm-list-scroll">
                {loading && <div className="comm-empty">Chargement…</div>}
                {!loading && tickets.length === 0 && (
                  <div className="comm-empty">
                    <div style={{ fontSize: 32, marginBottom: 8 }}>💬</div>
                    <div style={{ fontWeight: 600 }}>Aucun ticket</div>
                    <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 4 }}>Créez votre premier ticket de support</div>
                  </div>
                )}
                {tickets
                  .filter(t => ticketFilter.size > 0 && ticketFilter.has(t.status))
                  .filter(t => {
                    if (!ticketSearch.trim()) return true;
                    const q = ticketSearch.toLowerCase();
                    return (
                      t.subject?.toLowerCase().includes(q) ||
                      t.ticket_number?.toLowerCase().includes(q) ||
                      t.user_name?.toLowerCase().includes(q)
                    );
                  })
                  .map(t => (
                  <div
                    key={t.id}
                    className={`comm-list-row${selectedTicket === t.id ? ' selected' : ''}`}
                    onClick={() => openTicket(t.id)}
                  >
                    <UserAvatar name={t.user_name ?? t.user_email ?? user?.displayName ?? user?.email} size={38} />
                    <div className="comm-list-row-body">
                      <div className="comm-list-row-top">
                        <span className="comm-list-row-name">{t.user_name ?? 'Moi'}</span>
                        <StatusBadge status={t.status} />
                      </div>
                      <div className="comm-list-row-subject">{t.ticket_number} — {t.subject}</div>
                      <TicketChips ticketType={t.ticket_type} />
                      {t.last_body && (
                        <div className="comm-list-row-preview" style={{ visibility: 'hidden' }}>{stripPreview(t.last_body)}</div>
                      )}
                      <div className="comm-list-row-meta">
                        <span>{t.message_count} message{t.message_count > 1 ? 's' : ''}</span>
                        <span>{timeAgo(t.last_message_at ?? t.created_at)}</span>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </>
          )}

          {tab === 'notifications' && (() => {
            const NOTIF_PER_PAGE = 10;
            const filteredNotifs = notifFilter.size === 0
              ? []
              : notifFilter.size === 2
                ? notifs
                : notifFilter.has('unread') ? notifs.filter(n => !n.read) : notifs.filter(n => n.read);
            const totalNotifs    = filteredNotifs.length;
            const lastPage       = Math.max(0, Math.ceil(totalNotifs / NOTIF_PER_PAGE) - 1);
            const pagedNotifs    = filteredNotifs.slice(notifPage * NOTIF_PER_PAGE, (notifPage + 1) * NOTIF_PER_PAGE);
            const start          = totalNotifs === 0 ? 0 : notifPage * NOTIF_PER_PAGE + 1;
            const end            = Math.min((notifPage + 1) * NOTIF_PER_PAGE, totalNotifs);
            return (
              <>
                <div className="comm-list-header">
                  <div className="comm-topbar-filters">
                    {[['unread','Non lues'],['read','Lues']].map(([v,l]) => {
                      const active = notifFilter.has(v);
                      return (
                        <button
                          key={v}
                          className="comm-topbar-filter-btn"
                          style={active ? {
                            background: 'var(--primary)',
                            color: '#fff',
                            borderColor: 'var(--primary)',
                            display: 'flex', alignItems: 'center', gap: 5,
                          } : {}}
                          onClick={() => {
                            setNotifFilter(prev => {
                              const next = new Set(prev);
                              if (next.has(v)) { next.delete(v); } else { next.add(v); }
                              return next;
                            });
                            setNotifPage(0);
                          }}
                        >
                          {active && (
                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" style={{ flexShrink: 0 }}>
                              <polyline points="20 6 9 17 4 12"/>
                            </svg>
                          )}
                          {l}
                        </button>
                      );
                    })}
                  </div>
                </div>
                <div className="comm-list-scroll">
                  {notifs.length === 0 && (
                    <div className="comm-empty">
                      <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--border)" strokeWidth="1.5"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
                      <div style={{ marginTop: 12, color: 'var(--text-muted)', fontSize: 14 }}>Aucune notification</div>
                    </div>
                  )}
                  {pagedNotifs.map(n => (
                    <div
                      key={n.id}
                      className={`comm-list-row${selectedNotif?.id === n.id ? ' selected' : ''}${!n.read ? ' unread' : ''}`}
                      onClick={() => openNotif(n)}
                    >
                      <NotifTypeAvatar type={n.type} size={38} />
                      <div className="comm-list-row-body">
                        <div className="comm-list-row-top">
                          <span className="comm-list-row-name">{n.title}</span>
                        </div>
                        <div>
                          <span style={{
                            fontSize: 11, padding: '2px 7px', borderRadius: 99, fontWeight: 600,
                            background: (TYPE_META[n.type] ?? TYPE_META.info).bg,
                            color: (TYPE_META[n.type] ?? TYPE_META.info).color,
                          }}>
                            {(TYPE_META[n.type] ?? TYPE_META.info).label}
                          </span>
                        </div>
                        <div className="comm-list-row-preview" style={{ visibility: 'hidden' }}>{n.body ? stripPreview(n.body) : ' '}</div>
                        <div className="comm-list-row-meta">
                          <span>{timeAgo(n.created_at)}</span>
                        </div>
                      </div>
                      {!n.read && <span className="comm-unread-dot" />}
                    </div>
                  ))}
                </div>
                {totalNotifs > NOTIF_PER_PAGE && (
                  <div className="comm-list-pagination">
                    <span className="comm-list-pagination-info">
                      {start}–{end} sur {totalNotifs}
                    </span>
                    <div className="comm-list-pagination-btns">
                      <button disabled={notifPage === 0} onClick={() => setNotifPage(p => p - 1)}>
                        Précédent
                      </button>
                      <button disabled={notifPage >= lastPage} onClick={() => setNotifPage(p => p + 1)}>
                        Suivant
                      </button>
                    </div>
                  </div>
                )}
              </>
            );
          })()}
        </div>

        {/* ── Panneau détail droite ── */}
        <div className="comm-detail">

          {/* Détail ticket */}
          {tab === 'support' && thread && (
            <>
              <div className="comm-detail-header" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 0 }}>
                {/* Ligne 1 : type chip + titre + badge statut */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2, flexWrap: 'wrap' }}>
                  {thread.ticket.ticket_type && (() => {
                    const type = TICKET_TYPES.find(t => t.value === thread.ticket.ticket_type);
                    return type ? (
                      <span className="ticket-chip ticket-chip-type" style={TYPE_COLORS[thread.ticket.ticket_type] || {}}>
                        {type.label}
                      </span>
                    ) : null;
                  })()}
                  <span style={{ fontWeight: 700, fontSize: 15, flex: 1, minWidth: 0 }}>
                    {thread.ticket.ticket_number} — {thread.ticket.subject}
                  </span>
                  <StatusBadge status={thread.ticket.status} />
                </div>
                {/* Ligne 2 : meta gauche + chips pages droite */}
                {(() => {
                  const pages = thread.ticket.ticket_pages
                    ? (Array.isArray(thread.ticket.ticket_pages) ? thread.ticket.ticket_pages : JSON.parse(thread.ticket.ticket_pages))
                    : [];
                  return (
                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 25 }}>
                      <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
                        Ouvert par <strong style={{ color: 'var(--text)' }}>{thread.ticket.user_name}</strong> · {fmtDate(thread.ticket.created_at)}
                        {thread.ticket.assigned_name && (
                          <> — assigné à <strong style={{ color: 'var(--text)' }}>{thread.ticket.assigned_name}</strong></>
                        )}
                      </span>
                      <div className="ticket-chips" style={{ margin: 0 }}>
                        {pages.map(p => {
                          const page = TICKET_PAGES.find(x => x.value === p);
                          return page ? <span key={p} className="ticket-chip ticket-chip-page">{page.label}</span> : null;
                        })}
                      </div>
                    </div>
                  );
                })()}
              </div>

              <div className="comm-thread">
                {thread.messages.map(msg => (
                  <div key={msg.id} className={`comm-message${msg.is_admin ? ' from-admin' : ''}`}>
                    <UserAvatar name={msg.author_name} isAdmin={!!msg.is_admin} size={34} />
                    <div className="comm-message-content">
                      <div className="comm-message-meta">
                        <span style={{ fontWeight: 600, fontSize: 13 }}>{msg.author_name}</span>
                        {!!msg.is_admin && (
                          <span style={{ fontSize: 11, color: 'var(--primary)', fontWeight: 600 }}>Support</span>
                        )}
                        <span style={{ fontSize: 12, color: 'var(--text-muted)', marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>
                          {msg.updated_at && (
                            <span style={{ fontSize: 11, color: 'var(--text-muted)', fontStyle: 'italic' }}>modifié</span>
                          )}
                          {timeAgo(msg.created_at)}
                          {canEditMsg(msg) && editingMsg?.id !== msg.id && (
                            <button
                              type="button"
                              className="comm-edit-btn"
                              title={`Modifier (encore ${fmtRemaining(msg)})`}
                              onClick={() => setEditingMsg({ id: msg.id, body: msg.body })}
                            >
                              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                            </button>
                          )}
                        </span>
                      </div>
                      {editingMsg?.id === msg.id ? (
                        <div className="comm-edit-block">
                          <RichTextEditor
                            value={editingMsg.body}
                            onChange={body => setEditingMsg(e => ({ ...e, body }))}
                            placeholder="Modifiez votre message…"
                            minHeight={80}
                          />
                          <div className="comm-edit-actions">
                            <span className="comm-edit-timer">
                              ⏱ {fmtRemaining(msg) ?? 'Délai expiré'}
                            </span>
                            <button type="button" className="btn btn-ghost btn-sm"
                              onClick={() => setEditingMsg(null)}>Annuler</button>
                            <button type="button" className="btn btn-primary btn-sm"
                              disabled={editSaving || !editingMsg.body.replace(/<[^>]*>/g,'').trim() || !canEditMsg(msg)}
                              onClick={() => saveEdit(msg)}>
                              {editSaving ? 'Sauvegarde…' : 'Sauvegarder'}
                            </button>
                          </div>
                        </div>
                      ) : (
                        <MessageBody
                          html={msg.body}
                          onExpand={(src, name) => setLightbox({ url: src, name })}
                          ticketNumber={thread.ticket.ticket_number}
                          msgDate={msg.created_at}
                        />
                      )}
                      {msg.attachments?.length > 0 && (() => {
                        const images = msg.attachments.filter(a => a.mime_type?.startsWith('image/'));
                        const files  = msg.attachments.filter(a => !a.mime_type?.startsWith('image/'));
                        return (
                          <>
                            {images.length > 0 && (
                              <div className="comm-attach-images">
                                {images.map(a => (
                                  <AttachmentImage
                                    key={a.id}
                                    filename={a.filename}
                                    originalName={a.original_name}
                                    size={a.size}
                                    onExpand={(url, name) => setLightbox({ url, name })}
                                    ticketNumber={thread.ticket.ticket_number}
                                    msgDate={msg.created_at}
                                  />
                                ))}
                              </div>
                            )}
                            {files.length > 0 && (
                              <div className="comm-attachments">
                                {files.map(a => (
                                  <a
                                    key={a.id}
                                    href={`/api/tickets/attachments/${a.filename}`}
                                    target="_blank"
                                    rel="noreferrer"
                                    className="comm-attach-chip"
                                  >
                                    <span style={{ fontSize: 16 }}><FileIcon mime={a.mime_type} /></span>
                                    <span className="comm-attach-name">{a.original_name}</span>
                                    <span className="comm-attach-size">{fmtSize(a.size)}</span>
                                  </a>
                                ))}
                              </div>
                            )}
                          </>
                        );
                      })()}
                    </div>
                  </div>
                ))}
              </div>

              {/* Zone réponse */}
              {(thread.ticket.status === 'open' || isAdmin) && (
                <form className="comm-reply-form" onSubmit={submitReply}>
                  <RichTextEditor
                    value={replyBody}
                    onChange={setReplyBody}
                    onImgExpand={(url, name) => setLightbox({ url, name })}
                    placeholder="Votre réponse… (Ctrl+V pour coller une image)"
                    minHeight={90}
                  />
                  {replyFiles.length > 0 && (
                    <div className="comm-files-preview">
                      {replyFiles.map((f, i) => (
                        <FileChip key={i} file={f} onRemove={() => setReplyFiles(prev => prev.filter((_, j) => j !== i))} />
                      ))}
                    </div>
                  )}
                  <div className="comm-reply-actions">
                    <button
                      type="button"
                      className="btn btn-ghost btn-sm"
                      onClick={() => replyFileRef.current?.click()}
                    >
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
                      Joindre
                    </button>
                    <input
                      ref={replyFileRef}
                      type="file"
                      multiple
                      style={{ display: 'none' }}
                      onChange={e => setReplyFiles(prev => [...prev, ...Array.from(e.target.files)])}
                    />
                    <button type="submit" className="btn btn-primary btn-sm" disabled={replySending || !replyBody.replace(/<[^>]*>/g,'').trim()}>
                      {replySending ? 'Envoi…' : 'Répondre'}
                    </button>
                  </div>
                </form>
              )}
              {thread.ticket.status === 'resolved' && !isAdmin && (
                <div style={{ padding: '12px 20px', color: 'var(--text-muted)', fontSize: 13, textAlign: 'center', borderTop: '1px solid var(--border)' }}>
                  Ce ticket est résolu. Créez un nouveau ticket si besoin.
                </div>
              )}
            </>
          )}

          {/* Détail notification */}
          {tab === 'notifications' && selectedNotif && (
            <div className="comm-notif-detail">
              <div className="comm-detail-header">
                <NotifTypeAvatar type={selectedNotif.type} size={42} />
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 700, fontSize: 15 }}>{selectedNotif.title}</div>
                  <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
                    {timeAgo(selectedNotif.created_at)} · {(TYPE_META[selectedNotif.type] ?? TYPE_META.info).label}
                  </div>
                </div>
              </div>
              {selectedNotif.body && (
                <div style={{
                  background: '#fff',
                  borderTop: '1px solid var(--border)',
                  borderBottom: '1px solid var(--border)',
                  padding: '16px 24px',
                  height: 250,
                  overflowY: 'auto',
                  color: 'var(--text)',
                  lineHeight: 1.6,
                  fontSize: 14,
                }}>
                  {selectedNotif.body}
                </div>
              )}
              <div style={{
                display: 'flex',
                justifyContent: 'flex-end',
                alignItems: 'center',
                gap: 8,
                padding: '10px 16px',
                background: '#fff',
                borderBottom: '1px solid var(--border)',
              }}>
                <button
                  className="btn btn-sm btn-ghost"
                  style={{ color: 'var(--danger)' }}
                  onClick={() => deleteNotif(selectedNotif.id)}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ marginRight: 5 }}><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
                  Supprimer
                </button>
                {selectedNotif.link && (
                  <button className="btn btn-primary btn-sm" onClick={() => navigate(selectedNotif.link)}>
                    Voir le détail →
                  </button>
                )}
              </div>
            </div>
          )}

          {/* Bloc broadcast admin (onglet notifications) */}
          {/* broadcast form moved to modal */}

          {/* KPIs support */}
          {tab === 'support' && !thread && (() => {
            const kpis = [
              { label: 'Total Tickets', count: tickets.length,                                      bg: '#f3f4f6', color: '#111827' },
              { label: 'En attente',    count: tickets.filter(t => t.status === 'pending').length,  bg: '#fefce8', color: '#a16207' },
              { label: 'Ouverts',       count: tickets.filter(t => t.status === 'open').length,     bg: '#ecfdf5', color: '#0d9488' },
              { label: 'Clôturés',      count: tickets.filter(t => t.status === 'resolved').length, bg: '#fff1f2', color: '#e11d48' },
            ];
            return (
              <div style={{ background: '#fff', height: '100%', display: 'flex', flexDirection: 'column' }}>
                <div style={{ padding: '28px 28px 20px' }}>
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16 }}>
                    {kpis.map(k => (
                      <div key={k.label} style={{ background: k.bg, borderRadius: 12, padding: '24px 16px', textAlign: 'center' }}>
                        <div style={{ fontSize: 32, fontWeight: 700, color: k.color, lineHeight: 1 }}>{k.count}</div>
                        <div style={{ marginTop: 8, fontSize: 13, fontWeight: 600, color: k.color }}>{k.label}</div>
                      </div>
                    ))}
                  </div>
                </div>
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12 }}>
                  <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--border)" strokeWidth="1.5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
                  <div style={{ color: 'var(--text-muted)', fontSize: 14 }}>Sélectionnez un ticket</div>
                </div>
              </div>
            );
          })()}
          {tab === 'notifications' && !selectedNotif && !showBroadcastForm && (
            <div className="comm-detail-empty">
              <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--border)" strokeWidth="1.5"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
              <div style={{ marginTop: 12, color: 'var(--text-muted)', fontSize: 14 }}>Sélectionnez une notification</div>
            </div>
          )}
        </div>

      </div>{/* end comm-wrap */}
      </div>{/* end comm-page */}

      {/* ── Modale Nouvelle notification (admin) ── */}
      {showBroadcastForm && isAdmin && (() => {
        const selectedMeta = TYPE_META[bcType] ?? TYPE_META.info;
        return (
          <div className="comm-modal-backdrop" onClick={() => setShowBroadcastForm(false)}>
            <div className="comm-modal" style={{ maxWidth: 500 }} onClick={e => e.stopPropagation()}>
              <div className="comm-modal-header">
                <span style={{ fontWeight: 700, fontSize: 15 }}>Nouvelle notification</span>
                <button className="comm-modal-close" onClick={() => setShowBroadcastForm(false)}>×</button>
              </div>
              <form onSubmit={submitBroadcast}>
                <div style={{ padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 14 }}>

                  {/* Type — liste déroulante custom */}
                  <div>
                    <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Type</div>
                    <TypeDropdown value={bcType} onChange={setBcType} />
                  </div>

                  {/* Destinataire — dropdown searchable */}
                  <div>
                    <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Destinataire</div>
                    <RecipientDropdown value={bcUserId} onChange={setBcUserId} users={users} />
                  </div>

                  {/* Titre */}
                  <div>
                    <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Titre *</div>
                    <input
                      className="comm-input"
                      style={{ width: '100%', boxSizing: 'border-box' }}
                      value={bcTitle}
                      onChange={e => setBcTitle(e.target.value)}
                      placeholder="Titre de la notification"
                      autoFocus
                      required
                    />
                  </div>

                  {/* Message */}
                  <div>
                    <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 6 }}>Message <span style={{ fontWeight: 400, textTransform: 'none' }}>(optionnel)</span></div>
                    <RichTextEditor
                      value={bcBody}
                      onChange={setBcBody}
                      onImgExpand={(url, name) => setLightbox({ url, name })}
                      placeholder="Corps du message… (Ctrl+V pour coller une image)"
                      minHeight={120}
                    />
                  </div>

                  {bcResult && (
                    <div style={{ fontSize: 13, color: bcResult.ok ? 'var(--success)' : 'var(--danger)' }}>
                      {bcResult.msg}
                    </div>
                  )}
                </div>
                <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, padding: '0 20px 16px' }}>
                  <button type="button" className="btn btn-ghost" onClick={() => setShowBroadcastForm(false)}>Annuler</button>
                  <button
                    type="submit"
                    className="btn btn-primary"
                    disabled={bcSending || !bcTitle.trim()}
                    style={{ background: selectedMeta.color, border: 'none' }}
                  >
                    {bcSending ? 'Envoi…' : 'Envoyer'}
                  </button>
                </div>
              </form>
            </div>
          </div>
        );
      })()}

      {/* ── Menu ⋮ notifications ── */}
      {notifMenuPos && (
        <>
          <div style={{ position: 'fixed', inset: 0, zIndex: 299 }} onClick={() => setNotifMenuPos(null)} />
          <div style={{
            position: 'fixed', left: notifMenuPos.x, top: notifMenuPos.y,
            transform: 'translateX(-100%) translateY(4px)',
            zIndex: 300,
            background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
            padding: '4px 0', minWidth: 180,
          }}>
            {selectedNotif && !selectedNotif.read && (
              <button
                style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                onMouseLeave={e => e.currentTarget.style.background = 'none'}
                onClick={async () => {
                  await api.patch(`/notifications/${selectedNotif.id}/read`);
                  setNotifs(prev => prev.map(n => n.id === selectedNotif.id ? { ...n, read: 1 } : n));
                  setSelectedNotif(prev => ({ ...prev, read: 1 }));
                  setNotifMenuPos(null);
                }}
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg>
                Marquer comme lu
              </button>
            )}
            {unreadCount > 0 && (
              <button
                style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                onMouseLeave={e => e.currentTarget.style.background = 'none'}
                onClick={() => { markAllRead(); setNotifMenuPos(null); }}
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg>
                Tout considérer comme lu
              </button>
            )}
            <button
              style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--danger)', textAlign: 'left' }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
              onMouseLeave={e => e.currentTarget.style.background = 'none'}
              onClick={() => { deleteAllNotifs(); setNotifMenuPos(null); }}
            >
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
              Tout supprimer
            </button>
          </div>
        </>
      )}

      {/* ── Menu ⋮ support ── */}
      {supportMenuPos && (
        <>
          <div style={{ position: 'fixed', inset: 0, zIndex: 299 }} onClick={() => setSupportMenuPos(null)} />
          <div style={{
            position: 'fixed', left: supportMenuPos.x, top: supportMenuPos.y,
            transform: 'translateX(-100%) translateY(4px)',
            zIndex: 300,
            background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
            padding: '4px 0', minWidth: 180,
          }}>
            <button
              style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
              onMouseLeave={e => e.currentTarget.style.background = 'none'}
              onClick={() => { setShowCompose(true); setSupportMenuPos(null); }}
            >
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
              Nouveau ticket
            </button>
            {thread && (
              <button
                style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                onMouseLeave={e => e.currentTarget.style.background = 'none'}
                onClick={() => {
                  setCategType(thread.ticket.ticket_type ?? null);
                  const p = thread.ticket.ticket_pages;
                  setCategPages(p ? (Array.isArray(p) ? p : JSON.parse(p)) : []);
                  setShowCategModal(true);
                  setSupportMenuPos(null);
                }}
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                Modifier la catégorisation
              </button>
            )}
            {thread && isAdmin && (
              <>
                <button
                  style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'none'}
                  onClick={() => { setAssignSearch(''); setShowAssignModal(true); setSupportMenuPos(null); }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
                  Assigner à
                </button>
                {thread.ticket.status !== 'pending' ? (
                  <button
                    style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'none'}
                    onClick={() => { setTicketPending(); setSupportMenuPos(null); }}
                  >
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
                    Mettre en attente
                  </button>
                ) : (
                  <button
                    style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                    onMouseLeave={e => e.currentTarget.style.background = 'none'}
                    onClick={() => { toggleStatus(); setSupportMenuPos(null); }}
                  >
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-3.5"/></svg>
                    Réouvrir le ticket
                  </button>
                )}
                <button
                  style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text)', textAlign: 'left' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'none'}
                  onClick={() => { toggleStatus(); setSupportMenuPos(null); }}
                >
                  {thread.ticket.status === 'resolved'
                    ? <><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-3.5"/></svg>Rouvrir</>
                    : <><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="20 6 9 17 4 12"/></svg>Clôturer le ticket</>
                  }
                </button>
              </>
            )}
            {thread && (
              <>
                <div style={{ borderTop: '1px solid var(--border)', margin: '4px 0' }} />
                <button
                  style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 14px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)', textAlign: 'left' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'none'}
                  onClick={() => { setThread(null); setSelectedTicket(null); setSearchParams({}, { replace: true }); setSupportMenuPos(null); }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                  Fermer
                </button>
              </>
            )}
          </div>
        </>
      )}

      {/* ── Modale Assigner à ── */}
      {showAssignModal && (
        <div className="comm-modal-backdrop" onClick={() => setShowAssignModal(false)}>
          <div className="comm-modal" style={{ maxWidth: 420 }} onClick={e => e.stopPropagation()}>
            <div className="comm-modal-header">
              <span style={{ fontWeight: 700, fontSize: 15 }}>Assigner le ticket</span>
              <button className="comm-modal-close" onClick={() => setShowAssignModal(false)}>×</button>
            </div>
            <div style={{ padding: '12px 20px 16px' }}>
              <input
                className="comm-search"
                placeholder="Rechercher un administrateur…"
                value={assignSearch}
                onChange={e => setAssignSearch(e.target.value)}
                autoFocus
                style={{ width: '100%', marginBottom: 10 }}
              />
              {/* Désassigner */}
              {thread?.ticket.assigned_to && (
                <button
                  style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '8px 12px', background: 'none', border: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--danger)', borderRadius: 6, marginBottom: 4 }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'none'}
                  onClick={async () => { await assignTicket(null); setShowAssignModal(false); }}
                >
                  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
                  Retirer l'assignation
                </button>
              )}
              {/* Liste admins */}
              <div style={{ maxHeight: 260, overflowY: 'auto' }}>
                {users
                  .filter(u => !assignSearch.trim() || (u.display_name ?? u.email ?? '').toLowerCase().includes(assignSearch.toLowerCase()))
                  .map(u => {
                    const isAssigned = thread?.ticket.assigned_to === u.id;
                    return (
                      <button
                        key={u.id}
                        style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '8px 12px', background: isAssigned ? 'var(--surface-2)' : 'none', border: 'none', cursor: 'pointer', fontSize: 13, color: 'var(--text)', borderRadius: 6, textAlign: 'left' }}
                        onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                        onMouseLeave={e => e.currentTarget.style.background = isAssigned ? 'var(--surface-2)' : 'none'}
                        onClick={async () => { await assignTicket(u.id); setShowAssignModal(false); }}
                      >
                        <UserAvatar name={u.display_name ?? u.email} isAdmin size={30} />
                        <div>
                          <div style={{ fontWeight: 600 }}>{u.display_name ?? u.email}</div>
                          {u.display_name && <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{u.email}</div>}
                        </div>
                        {isAssigned && (
                          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" strokeWidth="2.5" style={{ marginLeft: 'auto' }}><polyline points="20 6 9 17 4 12"/></svg>
                        )}
                      </button>
                    );
                  })
                }
              </div>
            </div>
          </div>
        </div>
      )}

      {/* ── Modale catégorisation ticket ── */}
      {showCategModal && thread && (
        <div className="comm-modal-backdrop" onClick={() => setShowCategModal(false)}>
          <div className="comm-modal" style={{ maxWidth: 420 }} onClick={e => e.stopPropagation()}>
            <div className="comm-modal-header">
              <span style={{ fontWeight: 700, fontSize: 15 }}>Modifier la catégorisation</span>
              <button className="comm-modal-close" onClick={() => setShowCategModal(false)}>×</button>
            </div>
            <div style={{ padding: '16px 20px 20px', display: 'flex', flexDirection: 'column', gap: 16 }}>
              <div>
                <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.05em' }}>Type</div>
                <TagSelect label="Type" options={TICKET_TYPES} value={categType} onChange={setCategType} />
              </div>
              <div>
                <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.05em' }}>Pages concernées</div>
                <TagSelect label="Pages" options={TICKET_PAGES} value={categPages} onChange={setCategPages} multi />
              </div>
              <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 4 }}>
                <button className="btn btn-ghost" onClick={() => setShowCategModal(false)}>Annuler</button>
                <button
                  className="btn btn-primary"
                  onClick={async () => {
                    try {
                      await api.patch(`/tickets/${thread.ticket.id}/categorize`, {
                        ticket_type: categType,
                        ticket_pages: categPages.length ? JSON.stringify(categPages) : null,
                      });
                      setThread(prev => ({
                        ...prev,
                        ticket: {
                          ...prev.ticket,
                          ticket_type: categType,
                          ticket_pages: categPages.length ? categPages : null,
                        },
                      }));
                      setTickets(prev => prev.map(t => t.id === thread.ticket.id
                        ? { ...t, ticket_type: categType, ticket_pages: categPages.length ? JSON.stringify(categPages) : null }
                        : t
                      ));
                      setShowCategModal(false);
                    } catch { /* silencieux */ }
                  }}
                >
                  Enregistrer
                </button>
              </div>
            </div>
          </div>
        </div>
      )}

      {/* ── Lightbox image ── */}
      {lightbox && (
        <div className="comm-lightbox" onClick={() => setLightbox(null)}>
          <button className="comm-lightbox-close" onClick={() => setLightbox(null)}>×</button>
          <button
            className="comm-lightbox-download"
            onClick={e => {
              e.stopPropagation();
              const a = document.createElement('a');
              a.href = lightbox.url; a.download = lightbox.name; a.click();
            }}
          >
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
            Télécharger
          </button>
          <img
            src={lightbox.url}
            alt={lightbox.name}
            className="comm-lightbox-img"
            onClick={e => e.stopPropagation()}
          />
        </div>
      )}

      {/* ── Modale nouveau ticket ── */}
      {showCompose && (
        <div className="comm-modal-backdrop" onClick={() => setShowCompose(false)}>
          <div className="comm-modal" onClick={e => e.stopPropagation()}>
            <div className="comm-modal-header">
              <span style={{ fontWeight: 700, fontSize: 15 }}>Nouveau ticket de support</span>
              <button className="comm-modal-close" onClick={() => setShowCompose(false)}>×</button>
            </div>
            <form onSubmit={submitTicket} className="comm-modal-body">
              <div className="comm-form-group">
                <label className="comm-label">Sujet *</label>
                <input
                  className="comm-input"
                  value={composeSubject}
                  onChange={e => setComposeSubject(e.target.value)}
                  placeholder="Décrivez votre problème en quelques mots"
                  required
                  autoFocus
                />
              </div>
              <div className="comm-form-group">
                <label className="comm-label">Message *</label>
                <RichTextEditor
                  value={composeBody}
                  onChange={setComposeBody}
                  onImgExpand={(url, name) => setLightbox({ url, name })}
                  placeholder="Décrivez votre problème en détail… (Ctrl+V pour coller une image)"
                  minHeight={150}
                />
              </div>
              {composeFiles.length > 0 && (
                <div className="comm-files-preview">
                  {composeFiles.map((f, i) => (
                    <FileChip key={i} file={f} onRemove={() => setComposeFiles(prev => prev.filter((_, j) => j !== i))} />
                  ))}
                </div>
              )}
              <div className="comm-reply-actions">
                <button
                  type="button"
                  className="btn btn-ghost btn-sm"
                  onClick={() => fileInputRef.current?.click()}
                >
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
                  Joindre un fichier
                </button>
                <input
                  ref={fileInputRef}
                  type="file"
                  multiple
                  style={{ display: 'none' }}
                  onChange={e => setComposeFiles(prev => [...prev, ...Array.from(e.target.files)])}
                />
                <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginLeft: 'auto' }}>
                  <TagSelect label="Type" options={TICKET_TYPES} value={composeType} onChange={setComposeType} />
                  <TagSelect label="Page" options={TICKET_PAGES} value={composePages} onChange={setComposePages} multi />
                  <button type="submit" className="btn btn-primary btn-sm" disabled={composeSending === 'sending'}>
                    {composeSending === 'sending' ? 'Création…' : 'Créer le ticket'}
                  </button>
                </div>
              </div>
            </form>
          </div>
        </div>
      )}
    </>
  );
}
