// FOOTPASS Messenger v2 · chat screen: header, thread (stateful), composer, plus menu, recorder, keyboard
const { useState, useRef, useEffect, Fragment } = React;

function nowTime() {
  // Une seule source de temps (audit 360 P0-4) : l'univers du prototype vit le 18 juin 2026, 16:08
  return '16:08';
}
let _mid = 1000;
const mid = () => ++_mid;

// ──────────────────────────────────────────────────────────
// FILS · données par contact dans v2/convos.js (FP_THREADS / FP_REPLIES).
// fpTouchConvo : met à jour l'aperçu de la liste Messages et remonte le fil.
// ──────────────────────────────────────────────────────────
function fpTouchConvo(pid, preview) {
  const list = window.FP_CONVOS || [];
  const m = list.find((c) => c.id === pid) || { id: pid, preview: '', time: '', unread: 0 };
  m.preview = preview;
  m.time = nowTime();
  window.FP_CONVOS = [m, ...list.filter((c) => c.id !== pid)];
  if (window.m4TouchConvo) window.m4TouchConvo(pid, preview, m.time);
}

// ──────────────────────────────────────────────────────────
// HEADER
// ──────────────────────────────────────────────────────────
function ChatHeader({ dark, lang, peer, presence, onVoice, onVideo, onBack, onMore }) {
  const t = (k) => fpT(lang, k);
  const fg = dark ? '#EAEAEA' : '#0F0F0F';
  return (
    <div className={`header ${dark ? 'dark' : ''}`}>
      <button className="h-btn h-back" aria-label="Messages" onClick={onBack}><FPIcon.Back c={fg} /></button>
      <div className="h-identity">
        <div className="h-avatar-presence">
          <FPAvatar size={40} initials={peer.initials} src={peer.photo} />
          {peer.online ? <span className="presence-dot"></span> : null}
        </div>
        <div className="h-text">
          <div className="h-name-row"><span className="h-name">{window.fpUpperLast(peer.name)}</span></div>
          <div className="h-role">
            {presence === 'typing'
              ? <span className="h-online">{t('typing')}</span>
              : (peer.fam && window.m4RoleColor
                    ? <span><span style={{ color: window.m4RoleColor(peer.fam) }}>{peer.role}</span>{peer.org ? ` · ${peer.org}` : ''}</span>
                    : `${peer.role} · ${peer.org}`)}
          </div>
        </div>
      </div>
      <div className="h-actions">
        <button className="h-btn" aria-label={t('callVideo')} onClick={onVideo}><FPIcon.Video c={fg} s={24} /></button>
        <button className="h-btn" aria-label={t('callVoice')} onClick={onVoice}><FPIcon.Phone c={fg} /></button>
        <button className="h-btn" aria-label="Infos contact" onClick={onMore}><FPIcon.More c={fg} /></button>
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// TEXT BUBBLE · ticks, translation chip, ephemeral lifecycle
// ──────────────────────────────────────────────────────────
function TextBubble({ m, lang, showOriginal }) {
  const t = (k) => fpT(lang, k);
  if (m.expired) {
    return (
      <div className={`msg ${m.side}`} data-time={m.time}>
        <div className={`bubble ${m.side} expired-bubble`}>
          <FPIcon.Timer c="currentColor" s={12} />
          <span className="expired-text">{t('expired')}</span>
        </div>
      </div>
    );
  }
  return (
    <div className={`msg ${m.side}`} data-time={m.time}>
      <div className={`bubble ${m.side} ${m.ephemeral ? 'ephemeral' : ''}`}>
        <span className="bubble-text" key={showOriginal ? 'o' : 't'}>{showOriginal && m.original ? m.original : m.text}</span>
        <span className="bubble-meta">
          {m.ephemeral && <span className="eph-flag"><FPIcon.Timer c="currentColor" s={11} /></span>}
          <span className="bubble-time">{m.time}</span>
          {m.side === 'sent' && <Ticks status={m.status} />}
        </span>
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// AUDIO BUBBLE · sent or received
// ──────────────────────────────────────────────────────────
function AudioBubble({ m }) {
  const [playing, setPlaying] = useState(false);
  const [progress, setProgress] = useState(0.3);
  useEffect(() => {
    if (!playing) return;
    const id = setInterval(() => setProgress(p => (p >= 1 ? 0 : p + 0.02)), 120);
    return () => clearInterval(id);
  }, [playing]);
  const bars = [0.25,0.45,0.7,0.55,0.35,0.5,0.8,0.65,0.4,0.3,0.55,0.75,0.9,0.65,0.5,0.35,0.55,0.8,0.7,0.45,0.28,0.5,0.65,0.8,0.55,0.4,0.28,0.22];
  return (
    <div className={`msg ${m.side}`} data-time={m.time}>
      <div className={`bubble ${m.side} audio`}>
        <button className="audio-play" onClick={() => setPlaying(!playing)} aria-label="Lecture">
          {playing ? <FPIcon.Pause c="#fff" /> : <FPIcon.Play c="#fff" />}
        </button>
        <div className="audio-body">
          <div className="wave">
            {bars.map((h, i) => (
              <span key={i} className={`wbar ${i / bars.length < progress ? 'played' : ''}`} style={{ height: `${h * 100}%` }}></span>
            ))}
          </div>
          <div className="audio-duration">{m.dur}</div>
        </div>
        {m.time && (
          <span className="bubble-meta audio-meta">
            <span className="bubble-time">{m.time}</span>
            {m.side === 'sent' && <Ticks status={m.status} />}
          </span>
        )}
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// COMPOSER
// ──────────────────────────────────────────────────────────
function Composer({ dark, lang, peer, focused, setFocused, mode, setMode, onSendText, confidential, confDur, draft, onDraftUsed }) {
  const [value, setValue] = useState('');
  // Transcription validée : le texte arrive dans le champ, prêt à être retouché
  useEffect(() => { if (draft) { setValue(draft); onDraftUsed && onDraftUsed(); } }, [draft]);
  const hasText = value.trim().length > 0;
  const fg = dark ? '#EAEAEA' : '#0F0F0F';
  const subtle = dark ? '#999' : '#8E8E93';
  const first = peer ? peer.name.split(' ')[0] : 'Lucas';
  const ph = lang === 'en' ? `Message to ${first}` : `Message à ${first}`;
  const send = () => {
    if (!hasText) return;
    onSendText(value.trim());
    setValue('');
  };
  return (
    <div className={`composer ${dark ? 'dark' : ''} ${focused ? 'focused' : ''} ${confidential ? 'confidential' : ''}`}>
      <div className="composer-inner">
        <button
          className={`compose-plus ${mode === 'plus' ? 'active' : ''}`}
          aria-label="Joindre"
          onClick={() => setMode(mode === 'plus' ? null : 'plus')}
        ><FPIcon.Plus c={fg} /></button>
        <div className="compose-field">
          {confidential && <span className="compose-conf-flag"><FPIcon.Timer c="currentColor" s={13} /><i className="conf-durlbl">{confDur}</i></span>}
          <input
            className="compose-input"
            placeholder={focused ? '' : ph}
            value={value}
            onChange={(e) => setValue(e.target.value)}
            onFocus={() => { setFocused(true); setMode(null); }}
            onBlur={() => setFocused(false)}
            onKeyDown={(e) => { if (e.key === 'Enter') send(); }}
          />
          {!hasText && (
            <button className="compose-inline-mic" aria-label="Enregistrement audio"
              onMouseDown={(e) => { e.preventDefault(); setMode('recording'); }}>
              <FPIcon.Mic c={subtle} />
            </button>
          )}
        </div>
        {!hasText ? (
          <button className="compose-voice-orb" aria-label="Audio"
            onMouseDown={(e) => { e.preventDefault(); setMode('transcribe'); }}>
            <svg width="18" height="14" viewBox="0 0 18 14" fill="none">
              {[4, 7, 10, 7, 4].map((h, i) => (
                <rect key={i} x={1 + i * 4} y={(14 - h) / 2} width="2" height={h} rx="1" fill={dark ? '#0F0F0F' : '#fff'}></rect>
              ))}
            </svg>
          </button>
        ) : (
          <button className="compose-send active" aria-label="Envoyer" onMouseDown={(e) => e.preventDefault()} onClick={send}>
            <FPIcon.Send c="#fff" />
          </button>
        )}
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// PLUS MENU · 9 items incl. business actions
// ──────────────────────────────────────────────────────────
function PlusMenu({ dark, lang, onAction, confidential }) {
  const t = (k) => fpT(lang, k);
  const sw = 1.5;
  const items = [
    { key: 'photo', label: t('photo'), icon: <svg width="22" height="20" viewBox="0 0 22 20" fill="none"><path d="M2 6a2 2 0 012-2h2.5l1.5-2h6l1.5 2H20a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" stroke="currentColor" strokeWidth={sw}></path><circle cx="12" cy="11" r="3.5" stroke="currentColor" strokeWidth={sw}></circle></svg> },
    { key: 'camera', label: t('camera'), icon: <svg width="22" height="20" viewBox="0 0 22 20" fill="none"><path d="M2 6a2 2 0 012-2h3l1.5-2h5L13 4h5a2 2 0 012 2v9a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" stroke="currentColor" strokeWidth={sw}></path><circle cx="11" cy="10.5" r="3.5" stroke="currentColor" strokeWidth={sw}></circle></svg> },
    { key: 'auth', label: t('auth'), icon: <FPIcon.Shield c="currentColor" s={20} /> },
    { key: 'agenda', label: t('agenda'), icon: <FPIcon.Calendar c="currentColor" s={20} /> },
    { key: 'player', label: t('player'), icon: <svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect x="2" y="1.5" width="18" height="19" rx="2.5" stroke="currentColor" strokeWidth={sw}></rect><circle cx="11" cy="8" r="3" stroke="currentColor" strokeWidth={sw}></circle><path d="M5.5 16.5c.8-2.3 3-3.5 5.5-3.5s4.7 1.2 5.5 3.5" stroke="currentColor" strokeWidth={sw} strokeLinecap="round"></path></svg> },
    { key: 'pass', label: t('pass'), icon: <svg width="22" height="18" viewBox="0 0 22 18" fill="none"><rect x="1.5" y="1.5" width="19" height="15" rx="2.5" stroke="currentColor" strokeWidth={sw}></rect><circle cx="7" cy="8" r="2.5" stroke="currentColor" strokeWidth={sw}></circle><path d="M3.5 14C3.5 12 5 10.5 7 10.5C9 10.5 10.5 12 10.5 14" stroke="currentColor" strokeWidth={sw}></path><path d="M13 6h6M13 9h6M13 12h4" stroke="currentColor" strokeWidth={sw} strokeLinecap="round"></path></svg>, accent: true },
    { key: 'loc', label: t('loc'), icon: <FPIcon.Pin c="currentColor" s={20} /> },
    { key: 'contact', label: t('contact'), icon: <FPIcon.User c="currentColor" s={21} /> },
    { key: 'confidential', label: t('confidential'), icon: <FPIcon.Timer c="currentColor" s={20} />, toggled: confidential },
  ];
  return (
    <div className={`plus-menu ${dark ? 'dark' : ''}`}>
      <div className="plus-menu-grid">
        {items.map((it) => (
          <button key={it.key}
            className={`plus-item ${it.accent ? 'accent' : ''} ${it.toggled ? 'toggled' : ''}`}
            onClick={() => onAction(it.key)}>
            <div className="plus-icon">{it.icon}</div>
            <div className="plus-label">{it.label}</div>
          </button>
        ))}
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// RECORDER · audio + live transcription (i18n, sends real payloads)
// ──────────────────────────────────────────────────────────
function Recorder({ dark, lang, onCancel, onSend, initialKind = 'audio' }) {
  const t = (k) => fpT(lang, k);
  const [secs, setSecs] = useState(0);
  const [kind, setKind] = useState(initialKind);
  useEffect(() => {
    const id = setInterval(() => setSecs(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);
  useEffect(() => { setKind(initialKind); setSecs(0); }, [initialKind]);
  const mm = String(Math.floor(secs / 60)).padStart(2, '0');
  const ss = String(secs % 60).padStart(2, '0');

  const TRANSCRIPT = "Lucas, je viens d'avoir la direction du Real au téléphone. Ils confirment l'offre et veulent organiser la visite médicale mardi prochain à Madrid. Je t'envoie les détails dans la journée.";
  const [txt, setTxt] = useState('');
  const [interim, setInterim] = useState('');
  const txtRef = useRef('');
  const txScrollRef = useRef(null);
  useEffect(() => { txtRef.current = txt; }, [txt]);
  useEffect(() => { const el = txScrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [txt, interim]);
  useEffect(() => {
    if (kind !== 'transcribe') { setTxt(''); setInterim(''); return; }
    setTxt(''); setInterim('');
    const words = TRANSCRIPT.split(' ');
    let i = 0; let timer;
    const tick = () => {
      if (i >= words.length) { setInterim(''); return; }
      setInterim(words.slice(i, Math.min(i + 2, words.length)).join(' '));
      timer = setTimeout(() => {
        const w = words[i];
        setTxt(prev => (prev ? prev + ' ' : '') + w);
        setInterim('');
        i += 1;
        timer = setTimeout(tick, 60 + Math.random() * 90);
      }, 140 + Math.random() * 100);
    };
    tick();
    return () => clearTimeout(timer);
  }, [kind]);

  const bars = Array.from({ length: 36 }, (_, i) => i);
  const eqBars = Array.from({ length: 5 }, (_, i) => i);
  const durStr = `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, '0')}`;

  return (
    <div className={`recorder rec-v2 ${dark ? 'dark' : ''}`}>
      {kind === 'audio' ? (
        <div className="rec-row">
          <button className="rec-cancel" onClick={onCancel} aria-label={t('cancel')}>
            <svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M16 6L6 16M6 6l10 10" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"></path></svg>
          </button>
          <div className="rec-body">
            <div className="rec-pulse"></div>
            <div className="rec-time">{mm}:{ss}</div>
            <div className="rec-wave">
              {bars.map(i => <span key={i} className="rec-wbar" style={{ animationDelay: `${(i % 12) * 80}ms` }}></span>)}
            </div>
          </div>
          <button className="rec-send rec-send-audio" onClick={() => onSend('audio', { dur: durStr })} aria-label="Envoyer">
            <FPIcon.Send c="#fff" />
          </button>
        </div>
      ) : (
        <div className="rec-transcribe">
          <div className="rec-tx-head">
            <div className="rec-tx-eq">
              {eqBars.map(i => <span key={i} className="rec-tx-eqbar" style={{ animationDelay: `${i * 110}ms` }}></span>)}
            </div>
            <div className="rec-tx-label">
              <div className="rec-tx-title">{t('listening')}</div>
            </div>
            <button className="rec-tx-close" onClick={onCancel} aria-label={t('cancel')}>
              <svg width="18" height="18" viewBox="0 0 22 22" fill="none"><path d="M16 6L6 16M6 6l10 10" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"></path></svg>
            </button>
          </div>
          <div className="rec-tx-text" ref={txScrollRef}>
            {txt ? txt.split(' ').map((w, i) => <span key={i} className="rec-tx-w">{w} </span>) : null}
            {interim ? <span className="rec-tx-interim">{interim}</span> : null}
            <span className="rec-tx-caret"></span>
          </div>
          <div className="rec-tx-actions">
            <button className="rec-tx-secondary" onClick={() => onSend('draft', { text: txtRef.current })}>
              <FPIcon.Pen c="currentColor" s={13} />
              {t('txValidate')}
            </button>
            <button className="rec-tx-primary" onClick={() => onSend('text', { text: txtRef.current })}>
              <FPIcon.Send c="#fff" />
              {t('txSend')}
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────
// CHAT · owns message state, simulations, gestures
// ──────────────────────────────────────────────────────────
function ChatScreen({ dark, lang, peer: peerProp, forcedFocused, forcedMode, onVoice, onVideo, onBack }) {
  const t = (k) => fpT(lang, k);
  const peer = peerProp || FP_PEERS.lucas;
  // Un fil par contact · initialisé depuis FP_THREADS, vit le temps de la session
  const [store, setStore] = useState(() => {
    const o = {};
    Object.keys(FP_THREADS).forEach((k) => { o[k] = FP_THREADS[k].slice(); });
    return o;
  });
  const messages = store[peer.id] || [];
  const setMsgs = (pid, fn) => setStore((s) => ({ ...s, [pid]: fn(s[pid] || []) }));
  const peerRef = useRef(peer);
  useEffect(() => { peerRef.current = peer; }, [peer]);
  const [typingPid, setTypingPid] = useState(null);
  const typing = typingPid === peer.id;
  const [focusedState, setFocused] = useState(false);
  const [modeState, setMode] = useState(null);
  const [draft, setDraft] = useState('');
  const [showOrig, setShowOrig] = useState(false);
  const [globeChip, setGlobeChip] = useState(true);
  const [confidential, setConfidential] = useState(false);
  const [confDur, setConfDur] = useState('24 h');
  const [infoOpen, setInfoOpen] = useState(false);
  const [docOpen, setDocOpen] = useState(null);
  const [authPick, setAuthPick] = useState(false);   // choisir une de mes autorisations à envoyer
  const [authView, setAuthView] = useState(null);    // détail d'une autorisation reçue dans le fil
  const [toast, setToast] = useState(null);
  const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2200); };
  // Changement de contact : on referme les surfaces ouvertes
  useEffect(() => { setMode(null); setConfidential(false); setInfoOpen(false); setDocOpen(null); setShowOrig(false); setAuthPick(false); setAuthView(null); }, [peer.id]);
  const focused = forcedFocused !== undefined ? forcedFocused : focusedState;
  const mode = forcedMode !== undefined ? forcedMode : modeState;

  const showKeyboard = focused && mode !== 'recording' && mode !== 'transcribe';
  const showPlus = mode === 'plus';
  const showRecorder = mode === 'recording' || mode === 'transcribe';

  const threadRef = useRef(null);
  const timersRef = useRef([]);
  const later = (fn, ms) => { timersRef.current.push(setTimeout(fn, ms)); };
  useEffect(() => () => timersRef.current.forEach(clearTimeout), []);

  // ── Status lifecycle for a sent message ──
  const advanceStatus = (pid, id, ephemeral) => {
    later(() => setMsgs(pid, ms => ms.map(m => m.id === id ? { ...m, status: 'delivered' } : m)), 900);
    later(() => setMsgs(pid, ms => ms.map(m => m.id === id ? { ...m, status: 'read' } : m)), 2300);
    if (ephemeral) {
      later(() => setMsgs(pid, ms => ms.map(m => m.id === id ? { ...m, expired: true } : m)), 7500);
    }
  };

  const appendSent = (msg) => {
    const pid = peer.id;
    const id = mid();
    const full = { id, side: 'sent', time: nowTime(), status: 'sent', ephemeral: confidential, ...msg };
    setMsgs(pid, ms => (ms.length === 0
      ? [{ id: mid(), kind: 'day', label: "AUJOURD'HUI" }, full]
      : [...ms, full]));
    advanceStatus(pid, id, full.ephemeral && full.kind === 'text');
    fpTouchConvo(pid, full.kind === 'text' ? full.text : (FP_KIND_PREVIEW[full.kind] || 'Message'));
    return id;
  };

  const triggerReply = (delay = 3200) => {
    const pid = peer.id;
    later(() => setTypingPid(pid), delay - 1800);
    later(() => {
      setTypingPid(tp => (tp === pid ? null : tp));
      const pool = FP_REPLIES[pid] || FP_REPLIES.default;
      const r = pool[Math.floor(Math.random() * pool.length)];
      setMsgs(pid, ms => [...ms, { id: mid(), kind: 'text', side: 'received', time: nowTime(), ...r }]);
      fpTouchConvo(pid, r.text);
    }, delay);
  };

  const onEph = (on, dur) => {
    setConfidential(on);
    if (dur) setConfDur(dur);
    setMsgs(peer.id, (ms) => [...ms, { id: mid(), kind: 'day', label: on ? (lang === 'en' ? `DISAPPEARING MESSAGES · ${dur.toUpperCase()}` : `MESSAGES ÉPHÉMÈRES · ${dur.toUpperCase()}`) : (lang === 'en' ? 'DISAPPEARING MESSAGES OFF' : 'MESSAGES ÉPHÉMÈRES DÉSACTIVÉS') }]);
  };
  const onSendText = (text) => {
    appendSent({ kind: 'text', text });
    if (Math.random() < 0.7) triggerReply();
  };

  // ── Plus menu actions ──
  const onPlusAction = (key) => {
    const pid = peer.id;
    if (key === 'confidential') { setConfidential(c => !c); setModeAll(null); return; }
    setModeAll(null);
    if (key === 'photo' || key === 'camera') appendSent({ kind: 'photo' });
    if (key === 'loc') appendSent({ kind: 'location' });
    if (key === 'player') appendSent({ kind: 'player' });
    if (key === 'pass') appendSent({ kind: 'pass', who: { name: 'David MARTINEZ', id: 'FP-1102-3354-DM', initials: 'DM', role: 'Recruteur', org: 'Real Madrid', pid: 'david', fam: 'recruteur' } });
    if (key === 'contact') appendSent({ kind: 'pass', who: { name: 'Marco SILVA', id: 'FP-3310-7728-MS', initials: 'MS', role: 'Entraîneur', org: 'FC Porto', pid: 'marco', fam: 'entraineur' } });
    if (key === 'auth') setAuthPick(true);
    if (key === 'agenda') {
      const id = appendSent({
        kind: 'agenda', rsvp: 'pending',
        ev: { day: '01', month: lang === 'en' ? 'JUL' : 'JUIL', title: lang === 'en' ? 'Trial' : 'Essai', org: 'Real Madrid', when: lang === 'en' ? 'Wednesday · 09:30' : 'Mercredi · 09:30', where: 'Valdebebas, Madrid' },
      });
      later(() => setMsgs(pid, ms => ms.map(m => m.id === id ? { ...m, rsvp: 'confirmed' } : m)), 6000);
    }
  };

  const setModeAll = (v) => setMode(v);

  // ── Recorder send ──
  const onRecorderSend = (kind, payload) => {
    setModeAll(null);
    if (kind === 'draft') { setDraft(payload.text || ''); setFocused(true); return; }
    if (kind === 'audio') appendSent({ kind: 'audio', dur: payload.dur || '0:12' });
    if (kind === 'text' && payload.text) appendSent({ kind: 'text', text: payload.text });
    if (kind === 'both') {
      appendSent({ kind: 'audio', dur: payload.dur || '0:12' });
      if (payload.text) later(() => appendSent({ kind: 'text', text: payload.text }), 350);
    }
    triggerReply(4000);
  };

  // ── External events (tweaks / call logs) ──
  useEffect(() => {
    const handler = (e) => {
      const d = e.detail || {};
      if (d.type === 'incomingMsg') {
        const pid = d.pid || peerRef.current.id;
        setTypingPid(pid);
        later(() => {
          setTypingPid(tp => (tp === pid ? null : tp));
          const pool = FP_REPLIES[pid] || FP_REPLIES.default;
          const r = pool[Math.floor(Math.random() * pool.length)];
          setMsgs(pid, ms => [...ms, { id: mid(), kind: 'text', side: 'received', time: nowTime(), ...r }]);
          fpTouchConvo(pid, r.text);
        }, 1900);
      }
      if (d.type === 'calllog') {
        const pid = d.pid || peerRef.current.id;
        setMsgs(pid, ms => [...ms, { id: mid(), kind: 'calllog', callKind: d.kind, dur: d.dur }]);
      }
    };
    window.addEventListener('fp:event', handler);
    return () => window.removeEventListener('fp:event', handler);
  }, []);

  // Changement de conversation ou ouverture directe : refermer les feuilles ouvertes
  const [cardFiche, setCardFiche] = useState(null); // fiche ouverte depuis une carte partagée du fil
  useEffect(() => { const f = (e) => setCardFiche((e && e.detail) || null); window.addEventListener('fp:fiche', f); return () => window.removeEventListener('fp:fiche', f); }, []);
  useEffect(() => { setInfoOpen(false); setDocOpen(null); setCardFiche(null); }, [peer.id]);
  useEffect(() => {
    const f = () => { setInfoOpen(false); setDocOpen(null); setCardFiche(null); };
    window.addEventListener('fp:openchat', f);
    window.addEventListener('fp:call', f);
    return () => { window.removeEventListener('fp:openchat', f); window.removeEventListener('fp:call', f); };
  }, []);

  // ── Scroll pinned to bottom ──
  useEffect(() => {
    const pin = () => {
      const el = threadRef.current;
      if (!el) return;
      const prev = el.style.scrollBehavior;
      el.style.scrollBehavior = 'auto';
      el.scrollTop = el.scrollHeight;
      el.style.scrollBehavior = prev;
    };
    pin();
    requestAnimationFrame(pin);
    const id = setTimeout(pin, 250);
    return () => clearTimeout(id);
  }, [messages, typing, mode, focused, peer.id]);

  // ── Floating date pill + jump-to-bottom ──
  const [floatingDate, setFloatingDate] = useState('');
  const [showFloating, setShowFloating] = useState(false);
  const [showJump, setShowJump] = useState(false);
  useEffect(() => {
    const el = threadRef.current;
    if (!el) return;
    let hideTimer;
    const onScroll = () => {
      const labels = el.querySelectorAll('.day-label');
      const threadTop = el.getBoundingClientRect().top;
      let current = '';
      for (const lbl of labels) {
        if (lbl.getBoundingClientRect().top - threadTop <= 32) current = lbl.textContent;
        else break;
      }
      if (current) setFloatingDate(current);
      setShowFloating(true);
      setShowJump(el.scrollHeight - el.scrollTop - el.clientHeight > 280);
      clearTimeout(hideTimer);
      hideTimer = setTimeout(() => setShowFloating(false), 1400);
    };
    el.addEventListener('scroll', onScroll, { passive: true });
    return () => { el.removeEventListener('scroll', onScroll); clearTimeout(hideTimer); };
  }, []);

  const jumpToBottom = () => {
    const el = threadRef.current;
    if (!el) return;
    const prev = el.style.scrollBehavior;
    el.style.scrollBehavior = 'auto';
    el.scrollTop = el.scrollHeight;
    el.style.scrollBehavior = prev;
    setShowJump(false);
  };

  // ── Slide-left to reveal timestamps ──
  const [slideX, setSlideX] = useState(0);
  const dragRef = useRef({ active: false, startX: 0, startY: 0, axis: null });
  const onPD = (e) => { dragRef.current = { active: true, startX: e.clientX, startY: e.clientY, axis: null }; };
  const onPM = (e) => {
    const d = dragRef.current;
    if (!d.active) return;
    const dx = e.clientX - d.startX, dy = e.clientY - d.startY;
    if (d.axis === null) {
      if (Math.abs(dx) < 6 && Math.abs(dy) < 6) return;
      d.axis = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
    }
    if (d.axis === 'x') {
      e.preventDefault();
      let next = Math.min(0, dx);
      if (next < -56) next = -56 + (next + 56) * 0.25;
      setSlideX(Math.max(next, -72));
    }
  };
  const onPU = () => { dragRef.current.active = false; setSlideX(0); };

  // ── Render one message ──
  const renderMsg = (m) => {
    switch (m.kind) {
      case 'day': return <div key={m.id} className="day-label">{m.label}</div>;
      case 'text': return <TextBubble key={m.id} m={m} lang={lang} showOriginal={showOrig} />;
      case 'audio': return <AudioBubble key={m.id} m={m} />;
      case 'contract': return <ContractCard key={m.id} side={m.side} time={m.time} status={m.status} lang={lang} signState={m.signState} title={m.title} peerName={peer.name}
        onOpen={() => setDocOpen({ title: m.title || fpT(lang, 'contractTitle'), signState: m.signState })} />;
      case 'auth': return <AuthProofCard key={m.id} side={m.side} time={m.time} status={m.status} auth={m.auth} onOpen={() => setAuthView(m.auth)} />;
      case 'agenda': return <AgendaCard key={m.id} side={m.side} time={m.time} status={m.status} lang={lang} rsvp={m.rsvp} ev={m.ev} peerName={peer.name}
        onRsvp={(v) => { const pid = peer.id; setMsgs(pid, ms => ms.map(x => x.id === m.id ? { ...x, rsvp: v } : x)); triggerReply(2600); }} />;
      case 'player': return <PlayerCard key={m.id} side={m.side} time={m.time} status={m.status} lang={lang} />;
      case 'pass': return <PassIdCard key={m.id} side={m.side} time={m.time} status={m.status} lang={lang} who={m.who} />;
      case 'location': return <LocationCard key={m.id} side={m.side} time={m.time} status={m.status} lang={lang} />;
      case 'photo': return <PhotoMsg key={m.id} side={m.side} time={m.time} status={m.status} />;
      case 'calllog': return <CallLogMsg key={m.id} kind={m.callKind} dur={m.dur} lang={lang} />;
      default: return null;
    }
  };

  return (
    <div className={`app ${dark ? 'dark' : ''} ${showKeyboard || showPlus ? 'kb-open' : ''}`} data-screen-label="Conversation">
      <FPStatusBar />
      <ChatHeader dark={dark} lang={lang} peer={peer} presence={typing ? 'typing' : 'online'} onVoice={onVoice} onVideo={onVideo} onBack={onBack} onMore={() => setInfoOpen(true)} />
      <div className={`thread-floating-date ${showFloating && floatingDate ? 'on' : ''}`}><span>{floatingDate}</span></div>
      {globeChip && messages.some((m) => m.kind === 'text' && m.original) ? (
        <button className={`thread-globe ${showOrig ? 'on' : ''}`} aria-pressed={showOrig}
          aria-label={showOrig ? t('seeTranslation') : t('seeOriginal')}
          onClick={() => setShowOrig(o => !o)}><FPIcon.Globe c="currentColor" s={13} /></button>
      ) : null}
      <div className="thread" ref={threadRef}
        onPointerDown={onPD} onPointerMove={onPM} onPointerUp={onPU} onPointerCancel={onPU}
        style={{ '--slide-x': `${slideX}px` }}>
        {messages.length === 0 && !typing ? (
          <div className="chat-empty">
            <span className="chat-empty-ic"><FPIcon.Shield s={18} /></span>
            <b>Conversation sécurisée avec {peer.name.split(' ')[0]}</b>
            <span>Identité vérifiée Pass ID · messages chiffrés de bout en bout. Écrivez votre premier message.</span>
          </div>
        ) : null}
        {messages.map(renderMsg)}
        {typing && <TypingMsg />}
      </div>
      {showJump && !showKeyboard && !showPlus && !showRecorder && (
        <button className="jump-bottom" onClick={jumpToBottom} aria-label="Derniers messages">
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 5l5 5 5-5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"></path></svg>
        </button>
      )}
      {confidential && (
        <div className="conf-banner">
          <FPIcon.Timer c="currentColor" s={12} />
          <span>{t('confidentialOn')}</span>
          <button className="conf-dur" onClick={() => setConfDur((d) => d === '1 h' ? '24 h' : d === '24 h' ? '7 jours' : d === '7 jours' ? '1 mois' : '1 h')}>{confDur}</button>
          <button className="conf-off" onClick={() => setConfidential(false)} aria-label="Off">
            <svg width="12" height="12" viewBox="0 0 22 22" fill="none"><path d="M16 6L6 16M6 6l10 10" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round"></path></svg>
          </button>
        </div>
      )}
      {showRecorder ? (
        <Recorder dark={dark} lang={lang}
          initialKind={mode === 'transcribe' ? 'transcribe' : 'audio'}
          onCancel={() => setModeAll(null)} onSend={onRecorderSend} />
      ) : (
        <Composer dark={dark} lang={lang} peer={peer} focused={focused} setFocused={setFocused}
          mode={mode} setMode={setModeAll} onSendText={onSendText} confidential={confidential} confDur={confDur} draft={draft} onDraftUsed={() => setDraft('')} />
      )}
      {showPlus && <PlusMenu dark={dark} lang={lang} onAction={onPlusAction} confidential={confidential} />}
      {showKeyboard && !showPlus && <FPKeyboard dark={dark} lang={lang} />}
      {infoOpen ? (
        <PeerSheet peer={peer} onClose={() => setInfoOpen(false)} onVoice={onVoice} onVideo={onVideo}
          confidential={confidential} setConfidential={setConfidential} confDur={confDur} setConfDur={setConfDur} onEph={onEph} showToast={showToast}
          hasTranslated={messages.some((m) => m.kind === 'text' && m.original)}
          showOrig={showOrig} setShowOrig={setShowOrig} globeChip={globeChip} setGlobeChip={setGlobeChip} />
      ) : null}
      {cardFiche && window.FPStContact ? <window.FPStContact p={cardFiche.p} st={cardFiche.st} onClose={() => setCardFiche(null)} onSwap={(m) => setCardFiche({ p: m, st: cardFiche.st })} /> : null}
      {docOpen ? (
        <PgSheet inChat onClose={() => setDocOpen(null)} title={docOpen.title}>
          <div className="pg-info">
            <div className="pg-inforow"><span>Statut</span>{docOpen.signState === 'signed' ? <span className="pg-pill ok">Signé · horodaté</span> : <span className="pg-pill warn">En attente de signature</span>}</div>
            <div className="pg-inforow"><span>Chiffrement</span><b className="ok"><FPIcon.Shield s={11} /> AES-256 · E2EE</b></div>
            <div className="pg-inforow"><span>Archivage</span><b>Coffre-fort des deux parties</b></div>
          </div>
          <div className="pg-sheet-actions">
            <button className="pg-btn primary" onClick={() => { setDocOpen(null); showToast('Document déchiffré et ouvert'); }}>Ouvrir le document</button>
          </div>
        </PgSheet>
      ) : null}
      {authPick && window.FPAuthPickerSheet ? <window.FPAuthPickerSheet onClose={() => setAuthPick(false)} onPick={(a) => { setAuthPick(false); appendSent({ kind: 'auth', auth: a }); }} /> : null}
      {authView && window.FPAuthProofSheet ? <window.FPAuthProofSheet m={authView} onClose={() => setAuthView(null)} /> : null}
      {toast ? <div className="hm-toast">{toast}</div> : null}
    </div>
  );
}

Object.assign(window, { ChatScreen });
