// amigos-chat.jsx — Chat con un amigo (v20) con el acompañante Sereni.
// Cargado en SerenIA.html después de amigos.jsx: <script type="text/babel" src="./amigos-chat.jsx">
// Usa los helpers am* de amigos.jsx y los globales del HTML (Turtle, fmtTime, chatSeenMark, VoiceButton, SupaMessages…).

const AM_MSG_EMOJIS = ['❤️', '😂', '😮', '😢', '👍', '🔥'];
const AM_MSG_EMOJI_TIPS = { '❤️': 'Me gusta', '😂': 'Risa', '😮': 'Sorpresa', '😢': 'Tristeza', '👍': 'Apoyo', '🔥': 'Fuego' };
const amNormEmoji = e => e.replace(/️|︎/g, '');
const AM_GROUP_GAP_MS = 6 * 60000;

// Frases sugeridas según el ánimo visible del amigo (Sereni → "Ideas para escribir")
const AM_IDEAS = {
  low: [
    'Estoy aquí, sin prisa. ¿Quieres contarme cómo va tu día?',
    'No tienes que resolver nada ahorita. Solo quería que supieras que pienso en ti.',
    '¿Te late que respiremos juntos un par de minutos?',
    'Si hoy pesa, lo cargamos entre los dos.',
  ],
  mid: [
    '¡Hola! ¿Cómo amaneciste hoy?',
    'Pensé en ti y quise saludarte 🌿',
    '¿Qué fue lo mejor de tu día?',
    'Cuando tengas un rato, platicamos.',
  ],
  high: [
    '¡Qué gusto verte así de bien! Cuéntame qué pasó.',
    'Se nota tu buena energía hoy ✨',
    '¿Celebramos con una caminata o un café?',
    'Me alegra mucho leerte así.',
  ],
};
const AM_MSG_PAUSA = '🌿 ¿Hacemos una pausa juntos? Te propongo 2 minutos de respiración: inhala 4, sostén 4, exhala 6. Yo empiezo ahora.';
const AM_MSG_ABRAZO = '🐢 Un abrazo grande de mi parte. Aquí estoy.';
const AM_MSG_COMPANIA = '¿Me acompañas un momento? No me siento del todo bien y no quiero estar sin compañía ahora.';

function amClock(ts) { const d = new Date(ts); return isNaN(d.getTime()) ? '' : d.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit', hour12: false }); }
function amDayLabel(ts) {
  const d = new Date(ts); if (isNaN(d.getTime())) return '';
  const now = new Date(); const y = new Date(now); y.setDate(y.getDate() - 1);
  if (d.toDateString() === now.toDateString()) return 'Hoy';
  if (d.toDateString() === y.toDateString()) return 'Ayer';
  return d.toLocaleDateString('es-MX', { weekday: 'short', day: 'numeric', month: 'short' });
}
function amSereniPose(mood, typing) {
  if (typing) return 'chat';
  return { feliz: 'walk', calmo: 'meditate', neutral: 'sit', cansado: 'rest', ansioso: 'chat', triste: 'sad' }[mood] || 'chat';
}
function amIdeasFor(mood) {
  if (['triste', 'ansioso', 'cansado'].includes(mood)) return AM_IDEAS.low;
  if (['feliz', 'calmo'].includes(mood)) return AM_IDEAS.high;
  return AM_IDEAS.mid;
}

function FriendChat({ p, friend, onBack, userId, onOpenProfile }) {
  const [messages, setMessages] = React.useState([]);
  const [input, setInput] = React.useState('');
  const [loading, setLoading] = React.useState(true);
  const [sending, setSending] = React.useState(0);
  const [activeReact, setActiveReact] = React.useState(null);
  const [reactError, setReactError] = React.useState('');
  const [isFavorite, setIsFavorite] = React.useState(friend.isFavorite || false);
  const [locLoading, setLocLoading] = React.useState(false);
  const [showHeaderMenu, setShowHeaderMenu] = React.useState(false);
  const [muted, setMuted] = React.useState(() => userId ? isMutedFriend(userId, friend.id) : false);
  const [nick, setNick] = React.useState(() => userId ? getNickname(userId, friend.id) || friend.nickname || '' : friend.nickname || '');
  const [editingNick, setEditingNick] = React.useState(false);
  const [nickDraft, setNickDraft] = React.useState('');
  const [friendTyping, setFriendTyping] = React.useState(false);
  const [friendMood, setFriendMood] = React.useState(friend.mood || null);
  const [sereniOpen, setSereniOpen] = React.useState(false);
  const [sereniView, setSereniView] = React.useState('menu');   // 'menu' | 'ideas' | 'confirm-sos'
  const [sereniBusy, setSereniBusy] = React.useState(false);
  const [sereniMotion, setSereniMotion] = React.useState('');   // '' | 'hop' | 'nod'
  const [atBottom, setAtBottom] = React.useState(true);
  const [notice, setNotice] = React.useState('');
  const typingTimerRef = React.useRef(null);
  const lastTypingSentRef = React.useRef(0);
  const motionTimerRef = React.useRef(null);
  const scrollRef = React.useRef();
  const inputRef = React.useRef();
  const displayName = nick || friend.name;
  const firstName = amFirstName(friend.name);

  const flash = (t) => { setNotice(t); clearTimeout(flash._t); flash._t = setTimeout(() => setNotice(''), 2600); };
  const sereniDo = (kind, ms) => {
    setSereniMotion(kind);
    clearTimeout(motionTimerRef.current);
    motionTimerRef.current = setTimeout(() => setSereniMotion(''), ms || 900);
  };

  // Carga + realtime
  React.useEffect(() => {
    if (!userId || !friend.id || !window.SupaMessages) { setLoading(false); return; }
    window.SupaMessages.getConversation(userId, friend.id).then(msgs => {
      setMessages((msgs || []).map(m => ({ id: m.id, from: m.sender_id === userId ? 'me' : 'them', text: m.text, reactions: m.reactions || {}, created_at: m.created_at })));
      setLoading(false);
    }).catch(() => setLoading(false));

    const sub = window.SupaMessages.subscribe(userId, friend.id, (msg) => {
      if (msg.sender_id !== friend.id) return;                 // el canal recibe todo lo que llega a mí
      setMessages(prev => prev.some(m => m.id === msg.id) ? prev : [...prev, { id: msg.id, from: 'them', text: msg.text, reactions: msg.reactions || {}, created_at: msg.created_at }]);
      setFriendTyping(false);
      clearTimeout(typingTimerRef.current);
      chatSeenMark(userId, friend.id);
      amHaptic('light');
      sereniDo('hop', 1000);
    }, () => {
      setFriendTyping(true);
      clearTimeout(typingTimerRef.current);
      typingTimerRef.current = setTimeout(() => setFriendTyping(false), 3000);
    });

    // Ánimo actual del amigo (si su privacidad lo permite)
    if (window.SupaMood?.getLastForUsers) {
      window.SupaMood.getLastForUsers([friend.id]).then(m => {
        const fm = m && m[friend.id];
        setFriendMood(amCanSeeMood(friend.moodPrivacy || 'all', friend.isFavorite, fm) ? fm : null);
      }).catch(() => {});
    }

    return () => {
      try { sub?.unsubscribe?.(); } catch (_) {}
      clearTimeout(typingTimerRef.current);
      clearTimeout(motionTimerRef.current);
      if (userId && friend.id) chatSeenMark(userId, friend.id);
    };
  }, [userId, friend.id]);

  React.useEffect(() => {
    const el = scrollRef.current;
    if (el && atBottom) el.scrollTop = el.scrollHeight;
  }, [messages, friendTyping, loading]);

  const onScroll = () => {
    const el = scrollRef.current; if (!el) return;
    setAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 80);
  };

  // Envío (texto libre o acciones de Sereni)
  const sendText = async (text) => {
    text = (text || '').trim();
    if (!text) return false;
    amHaptic('send');
    const tempId = 'tmp-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6);
    const now = new Date().toISOString();
    setMessages(m => [...m, { id: tempId, from: 'me', text, reactions: {}, created_at: now }]);
    setAtBottom(true);
    sereniDo('nod', 600);
    if (!(userId && friend.id && window.SupaMessages)) return true;
    setSending(s => s + 1);
    try {
      const saved = await window.SupaMessages.send(userId, friend.id, text);
      if (saved?.id) setMessages(prev => prev.map(m => m.id === tempId ? { ...m, id: saved.id, created_at: saved.created_at || m.created_at } : m));
      return true;
    } catch (_) {
      setMessages(prev => prev.map(m => m.id === tempId ? { ...m, failed: true } : m));
      return false;
    } finally { setSending(s => Math.max(0, s - 1)); }
  };
  const send = async () => {
    const text = input.trim();
    if (!text) return;
    setInput('');
    if (inputRef.current) { inputRef.current.style.height = 'auto'; }
    await sendText(text);
  };
  const retry = async (m) => {
    setMessages(prev => prev.filter(x => x.id !== m.id));
    await sendText(m.text);
  };

  const handleToggleFav = async () => {
    const newFav = !isFavorite;
    setIsFavorite(newFav);
    amHaptic('light');
    if (userId && window.SupaFriends?.setFavorite) await window.SupaFriends.setFavorite(userId, friend.id, newFav).catch(() => {});
  };

  const handleLocation = () => {
    if (!navigator.geolocation) { setReactError('Tu dispositivo no soporta geolocalización.'); setTimeout(() => setReactError(''), 3000); return; }
    setLocLoading(true);
    navigator.geolocation.getCurrentPosition(
      async (pos) => {
        setLocLoading(false);
        const { latitude: lat, longitude: lng } = pos.coords;
        await sendText(`📍 Mi ubicación: https://www.google.com/maps?q=${lat.toFixed(6)},${lng.toFixed(6)}`);
      },
      () => { setLocLoading(false); setReactError('No se pudo obtener tu ubicación. Revisa los permisos.'); setTimeout(() => setReactError(''), 4000); },
      { timeout: 10000 }
    );
  };

  const handleReact = async (msgId, emoji) => {
    if (!userId || !window.SupaMessages || !msgId || msgId.startsWith('tmp-')) return;
    amHaptic('light');
    setActiveReact(null);
    try {
      const newReactions = await window.SupaMessages.react(msgId, userId, amNormEmoji(emoji));
      setMessages(prev => prev.map(m => m.id === msgId ? { ...m, reactions: newReactions } : m));
    } catch (err) {
      setReactError('No se pudo guardar la reacción: ' + (err?.message || 'error desconocido'));
      setTimeout(() => setReactError(''), 4000);
    }
  };

  const onInputChange = (e) => {
    setInput(e.target.value);
    const el = e.target; el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 112) + 'px';
    const now = Date.now();
    if (now - lastTypingSentRef.current > 1500 && userId && friend.id && window.SupaMessages?.broadcastTyping) {
      lastTypingSentRef.current = now;
      window.SupaMessages.broadcastTyping(userId, friend.id);
    }
  };

  // ── Acciones de Sereni ──
  const openSereni = () => { amHaptic('light'); setSereniView('menu'); setSereniOpen(true); sereniDo('nod', 500); };
  const closeSereni = () => { setSereniOpen(false); setSereniView('menu'); };
  const useIdea = (t) => {
    setInput(t); closeSereni();
    setTimeout(() => { const el = inputRef.current; if (el) { el.focus(); el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 112) + 'px'; } }, 60);
  };
  const sereniSend = async (text, done) => {
    if (sereniBusy) return;
    setSereniBusy(true);
    const ok = await sendText(text);
    setSereniBusy(false); closeSereni();
    flash(ok ? done : 'No se pudo enviar. Revisa tu conexión.');
  };
  const sereniSOS = async () => {
    if (sereniBusy || !userId) return;
    setSereniBusy(true);
    amHaptic('sos');
    let ok = true;
    try { if (window.SupaSOS?.trigger) await window.SupaSOS.trigger(userId, 'close', AM_MSG_COMPANIA); } catch (_) { ok = false; }
    // La RPC avisa a los contactos cercanos (★); si este amigo no lo es, también le llega directo
    if (!isFavorite) ok = (await sendText('🆘 ' + AM_MSG_COMPANIA)) && ok;
    else setMessages(prev => [...prev, { id: 'local-sos-' + Date.now(), from: 'me', text: '🆘 ' + AM_MSG_COMPANIA, reactions: {}, created_at: new Date().toISOString(), local: true }]);
    setSereniBusy(false); closeSereni();
    flash(ok ? 'Avisaste que necesitas compañía. No estás sola/o.' : 'No se pudo avisar. Intenta de nuevo.');
  };

  const moodColor = friendMood ? amMoodColor(friendMood.mood, p.primaryDeep) : null;
  const sereniPose = amSereniPose(friendMood?.mood, friendTyping);
  const sosMin = friend.sosAt ? Math.round((Date.now() - new Date(friend.sosAt).getTime()) / 60000) : null;
  const hasText = !!input.trim();

  // Agrupar por día y por remitente
  const items = [];
  messages.forEach((m, i) => {
    const prev = messages[i - 1], next = messages[i + 1];
    const t = new Date(m.created_at || 0).getTime();
    const sameDayPrev = prev && new Date(prev.created_at || 0).toDateString() === new Date(m.created_at || 0).toDateString();
    if (!sameDayPrev && m.created_at) items.push({ kind: 'day', key: 'day-' + (m.id || i), label: amDayLabel(m.created_at) });
    const first = !prev || prev.from !== m.from || !sameDayPrev || (t - new Date(prev.created_at || 0).getTime()) > AM_GROUP_GAP_MS;
    const last = !next || next.from !== m.from || new Date(next.created_at || 0).toDateString() !== new Date(m.created_at || 0).toDateString() || (new Date(next.created_at || 0).getTime() - t) > AM_GROUP_GAP_MS;
    items.push({ kind: 'msg', key: m.id, m, first, last });
  });

  const menuRow = (extra) => ({
    width: '100%', padding: '14px 16px', background: 'transparent', border: 'none', textAlign: 'left', fontSize: 14, color: p.ink,
    cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 12, fontFamily: 'inherit', borderBottom: `1px solid ${p.line}`, ...extra,
  });

  return (
    <div className="am-chat" style={{ display: 'flex', flexDirection: 'column', height: '100%', background: p.bg, position: 'relative', color: p.ink }}
      onClick={() => setActiveReact(null)}>
      <style>{`
        @keyframes amSereniHop { 0% { transform: translateY(0) scale(1); } 30% { transform: translateY(-14px) scale(1.06, 0.96) rotate(-4deg); } 55% { transform: translateY(0) scale(0.97, 1.04); } 75% { transform: translateY(-5px) rotate(3deg); } 100% { transform: translateY(0) scale(1); } }
        @keyframes amSereniNod { 0%, 100% { transform: rotate(0); } 35% { transform: rotate(-7deg) translateY(-2px); } 70% { transform: rotate(5deg); } }
        @keyframes amPing { 0% { transform: scale(0.9); opacity: .55; } 100% { transform: scale(1.6); opacity: 0; } }
        @keyframes amBubbleIn { from { transform: translateY(6px) scale(.98); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } }
        @keyframes amTypingDot { 0%, 60%, 100% { transform: translateY(0); opacity: .4; } 30% { transform: translateY(-4px); opacity: 1; } }
        @keyframes amFade { from { opacity: 0; } to { opacity: 1; } }
        @keyframes amSheetUp { from { transform: translateY(24px); opacity: .6; } to { transform: translateY(0); opacity: 1; } }
        @keyframes amRise { from { transform: translateY(6px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
        @keyframes amSpin { to { transform: rotate(360deg); } }
        .am-sereni { transition: transform .2s ease, opacity .2s ease, filter .2s ease; }
        .am-sereni.is-away { opacity: .62; transform: scale(.88); }
        .am-sereni:active { transform: scale(.94); }
        .am-sereni-img { animation: turtleBreath 4.5s ease-in-out infinite; transform-origin: center bottom; }
        .am-sereni.is-hop .am-sereni-img { animation: amSereniHop .9s cubic-bezier(.34,1.56,.64,1) 1; }
        .am-sereni.is-nod .am-sereni-img { animation: amSereniNod .6s ease-in-out 1; }
        .am-bubble { animation: amBubbleIn .18s ease; }
        .am-press { transition: transform .12s ease, opacity .12s ease; }
        .am-press:active { transform: scale(.94); }
        .am-textarea { resize: none; line-height: 1.35; }
        .am-textarea::placeholder { color: ${p.inkMute}; }
        .am-chat .msg-react-bar { background: ${p.surface}; border: 1px solid ${p.line}; }
        @media (prefers-reduced-motion: reduce) { .am-chat * { animation-duration: .01ms !important; transition-duration: .01ms !important; } }
      `}</style>

      {/* ── Cabecera ── */}
      <div style={{ padding: '6px 12px 10px 6px', borderBottom: `1px solid ${p.line}`, background: p.surface, display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
        <button onClick={onBack} aria-label="Volver" className="am-press" style={{ background: 'transparent', border: 'none', color: p.primaryDeep, fontSize: 26, lineHeight: 1, cursor: 'pointer', width: 38, height: 38, borderRadius: 12, display: 'grid', placeItems: 'center' }}>‹</button>
        <button onClick={() => onOpenProfile && onOpenProfile(friend)} style={{ display: 'flex', alignItems: 'center', gap: 10, flex: 1, minWidth: 0, background: 'transparent', border: 'none', padding: 0, cursor: onOpenProfile ? 'pointer' : 'default', textAlign: 'left', color: p.ink, fontFamily: 'inherit' }}>
          <AmAvatar p={p} pose={friendMood ? amMoodPose(friendMood.mood, friend.pose) : (friend.pose || 'sit')} size={42} dotColor={friend.sosAt ? AM_RED : moodColor} sos={!!friend.sosAt} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 15, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}>
              {displayName}
              {isFavorite && <span title="Contacto cercano" style={{ fontSize: 12, color: AM_STAR, marginLeft: 5 }}>★</span>}
              {nick && <span style={{ fontSize: 11, color: p.inkMute, marginLeft: 6, fontWeight: 400 }}>{friend.name}</span>}
            </div>
            <div style={{ fontSize: 11.5, marginTop: 2, display: 'flex', alignItems: 'center', gap: 5, overflow: 'hidden', whiteSpace: 'nowrap' }}>
              {friendTyping ? (
                <span style={{ color: p.primaryDeep, fontWeight: 600 }}>escribiendo…</span>
              ) : friendMood ? (
                <>
                  <span style={{ width: 7, height: 7, borderRadius: 4, background: moodColor, display: 'inline-block', flexShrink: 0 }} />
                  <span style={{ color: moodColor, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis' }}>{amMoodText(friendMood)}</span>
                </>
              ) : (
                <span style={{ color: p.inkMute }}>{friend.userType || 'Usuario'}</span>
              )}
            </div>
          </div>
        </button>
        <button onClick={handleToggleFav} title={isFavorite ? 'Quitar de cercanos' : 'Agregar a cercanos'} aria-label={isFavorite ? 'Quitar de cercanos' : 'Agregar a cercanos'}
          className={`fav-heart${isFavorite ? ' is-on' : ''}`}
          style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: '4px 6px', lineHeight: 1, position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 22 }}>
          ❤<span className="fh-bubble"></span><span className="fh-sparks"></span>
        </button>
        <button onClick={e => { e.stopPropagation(); setShowHeaderMenu(true); }} title="Opciones" aria-label="Opciones del chat" className="am-press"
          style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 36, height: 36, borderRadius: 10, background: 'transparent', border: 'none', cursor: 'pointer', color: p.inkSoft }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="12" cy="5" r="1.6" fill="currentColor" stroke="none" /><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none" /><circle cx="12" cy="19" r="1.6" fill="currentColor" stroke="none" />
          </svg>
        </button>
      </div>

      {friend.sosAt && sosMin != null && sosMin < 360 && (
        <div role="status" style={{ flexShrink: 0, margin: '10px 14px 0', padding: '10px 12px', borderRadius: 14, background: 'rgba(197,82,75,0.08)', border: '1px solid rgba(197,82,75,0.3)', fontSize: 12.5, color: AM_RED, display: 'flex', alignItems: 'center', gap: 8, animation: 'amRise .2s ease' }}>
          <span style={{ fontSize: 16 }}>🆘</span>
          <span><b>{firstName} pidió apoyo</b> hace {sosMin < 1 ? 'un momento' : `${sosMin} min`}. Un mensaje tuyo puede ayudar mucho.</span>
        </div>
      )}
      {notice && (
        <div role="status" style={{ flexShrink: 0, margin: '10px 14px 0', padding: '9px 12px', borderRadius: 12, background: p.primarySoft, color: p.primaryDeep, fontSize: 12.5, fontWeight: 500, animation: 'amRise .2s ease' }}>{notice}</div>
      )}

      {/* ── Mensajes ── */}
      <div ref={scrollRef} onScroll={onScroll} style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', padding: '12px 14px 0', WebkitOverflowScrolling: 'touch' }}>
        <div style={{ maxWidth: 720, margin: '0 auto' }}>
          {loading && <div style={{ textAlign: 'center', padding: 32, color: p.inkMute, fontSize: 13 }}>Cargando mensajes…</div>}
          {!loading && messages.length === 0 && (
            <div style={{ textAlign: 'center', padding: '40px 24px 24px', color: p.inkMute }}>
              <Turtle pose="chat" size={92} animate style={{ margin: '0 auto', filter: 'drop-shadow(0 8px 14px rgba(0,0,0,0.12))' }} />
              <div style={{ fontSize: 16, fontWeight: 600, color: p.ink, marginTop: 14, fontFamily: 'Fraunces, serif' }}>Todavía no hay mensajes</div>
              <div style={{ fontSize: 13, marginTop: 6, lineHeight: 1.6 }}>Di hola a {firstName} 👋<br />Este espacio es solo de ustedes dos. Si no sabes por dónde empezar, toca a Sereni.</div>
            </div>
          )}
          {items.map(it => {
            if (it.kind === 'day') return (
              <div key={it.key} style={{ textAlign: 'center', margin: '10px 0 12px' }}>
                <span style={{ fontSize: 10.5, fontWeight: 600, color: p.inkMute, background: p.surface, border: `1px solid ${p.line}`, padding: '3px 10px', borderRadius: 999, letterSpacing: '0.04em', textTransform: 'uppercase' }}>{it.label}</span>
              </div>
            );
            const { m, first, last } = it;
            const isMe = m.from === 'me';
            const reactionEntries = Object.entries(m.reactions || {}).filter(([, l]) => Array.isArray(l) && l.length > 0);
            const isPickerOpen = activeReact === m.id;
            const isSOS = typeof m.text === 'string' && m.text.startsWith('🆘');
            const mapsMatch = typeof m.text === 'string' && m.text.match(/📍 Mi ubicación: (https?:\/\/(?:www\.)?(?:maps\.google\.com|google\.com\/maps)[^\s]*q=([\d.-]+),([\d.-]+)[^\s]*)/);
            const radius = isMe
              ? `18px ${first ? 18 : 6}px 6px 18px`
              : `${first ? 18 : 6}px 18px 18px 6px`;
            const pending = String(m.id).startsWith('tmp-');
            return (
              <div key={it.key} style={{ marginBottom: last ? (reactionEntries.length ? 14 : 8) : 3, display: 'flex', flexDirection: 'column', alignItems: isMe ? 'flex-end' : 'flex-start' }}>
                <div style={{ position: 'relative', maxWidth: 'min(80%, 520px)' }}>
                  <div className="am-bubble"
                    onClick={e => { e.stopPropagation(); if (!pending && !m.local) setActiveReact(isPickerOpen ? null : m.id); }}
                    style={{
                      cursor: pending ? 'default' : 'pointer',
                      background: isSOS ? 'rgba(197,82,75,0.10)' : isMe ? p.primaryDeep : p.surface,
                      color: isSOS ? p.ink : isMe ? '#fff' : p.ink,
                      border: isSOS ? `1px solid rgba(197,82,75,0.4)` : isMe ? 'none' : `1px solid ${p.line}`,
                      padding: mapsMatch ? '9px 13px 5px' : '9px 13px', borderRadius: radius,
                      fontSize: 15, lineHeight: 1.42, userSelect: 'none', overflow: 'hidden',
                      opacity: pending ? 0.72 : m.failed ? 0.85 : 1, transition: 'opacity .2s',
                      boxShadow: isMe && !isSOS ? `0 2px 8px ${p.primaryDeep}2e` : 'none',
                    }}>
                    {mapsMatch ? (
                      <AmMapCard p={p} isMe={isMe} text={m.text} match={mapsMatch} />
                    ) : (
                      <span style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{m.text}</span>
                    )}
                  </div>
                  {isPickerOpen && (
                    <div onClick={e => e.stopPropagation()} className="msg-react-bar" style={{ position: 'absolute', bottom: '104%', [isMe ? 'right' : 'left']: 0, zIndex: 50 }}>
                      {AM_MSG_EMOJIS.map(emoji => {
                        const ne = amNormEmoji(emoji);
                        const mine = Object.entries(m.reactions || {}).some(([k, l]) => amNormEmoji(k) === ne && Array.isArray(l) && l.includes(userId));
                        const isHeart = ne === '❤';
                        return (
                          <button key={emoji} onClick={() => handleReact(m.id, emoji)} aria-label={AM_MSG_EMOJI_TIPS[emoji]}
                            className={`msg-react-btn${mine ? ' is-active' : ''}${isHeart ? ' is-heart' : ''}`} data-tip={AM_MSG_EMOJI_TIPS[emoji] || ''}>
                            <span className="msg-react-emoji">{emoji}</span>
                          </button>
                        );
                      })}
                    </div>
                  )}
                  {reactionEntries.length > 0 && (
                    <div style={{ display: 'flex', gap: 4, marginTop: -6, marginBottom: 2, justifyContent: isMe ? 'flex-end' : 'flex-start', flexWrap: 'wrap', padding: isMe ? '0 6px 0 0' : '0 0 0 6px', position: 'relative', zIndex: 1 }}>
                      {reactionEntries.map(([emoji, list]) => {
                        const mine = Array.isArray(list) && list.includes(userId);
                        const isHeart = amNormEmoji(emoji) === '❤';
                        return (
                          <button key={emoji} onClick={e => { e.stopPropagation(); handleReact(m.id, emoji); }} style={{
                            padding: '2px 7px', borderRadius: 999, fontSize: 12, background: mine ? p.primarySoft : p.bg,
                            border: `1px solid ${mine ? p.primaryDeep : p.line}`, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 3,
                            boxShadow: '0 1px 4px rgba(0,0,0,0.08)', fontFamily: 'inherit',
                          }}>
                            <span style={isHeart ? { color: '#e2264d' } : undefined}>{emoji}</span>
                            <span style={{ fontSize: 11, fontWeight: 600, color: mine ? p.primaryDeep : p.inkSoft }}>{list.length}</span>
                          </button>
                        );
                      })}
                    </div>
                  )}
                </div>
                {m.failed ? (
                  <button onClick={e => { e.stopPropagation(); retry(m); }} style={{ fontSize: 11, color: AM_RED, background: 'transparent', border: 'none', cursor: 'pointer', padding: '2px 6px', fontFamily: 'inherit', fontWeight: 600 }}>No se envió · Reintentar</button>
                ) : last && m.created_at ? (
                  <div style={{ fontSize: 10.5, color: p.inkMute, padding: '2px 6px 0', opacity: 0.85 }}>
                    {pending ? 'enviando…' : amClock(m.created_at)}{m.local ? ' · aviso a cercanos' : ''}
                  </div>
                ) : null}
              </div>
            );
          })}

          {friendTyping && (
            <div style={{ display: 'flex', alignItems: 'flex-end', gap: 8, marginBottom: 8, animation: 'amRise .2s ease' }}>
              <div style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '9px 13px', background: p.surface, borderRadius: '18px 18px 18px 6px', border: `1px solid ${p.line}` }}>
                {[0, 0.2, 0.4].map(d => <span key={d} style={{ width: 6, height: 6, borderRadius: '50%', background: p.inkSoft, display: 'inline-block', animation: `amTypingDot 1.2s ease-in-out ${d}s infinite` }} />)}
              </div>
              <span style={{ fontSize: 11, color: p.inkMute, paddingBottom: 6 }}>{firstName} está escribiendo…</span>
            </div>
          )}

          {/* Acompañante Sereni: pegajoso al pie de la lista, reserva su propio espacio (nunca tapa la última burbuja) */}
          <div style={{ position: 'sticky', bottom: 0, display: 'flex', justifyContent: 'flex-end', alignItems: 'flex-end', pointerEvents: 'none', paddingTop: 4, paddingBottom: 8, minHeight: 68 }}>
            <button type="button" onClick={e => { e.stopPropagation(); openSereni(); }} aria-label="Sereni: ideas para escribir y apoyo" title="Sereni"
              data-sereni-state={sereniMotion || (atBottom ? 'idle' : 'away')}
              className={`am-sereni${sereniMotion ? ' is-' + sereniMotion : ''}${!atBottom ? ' is-away' : ''}`}
              style={{ pointerEvents: 'auto', position: 'relative', width: 60, height: 60, padding: 0, border: 'none', borderRadius: '50%', cursor: 'pointer',
                background: `radial-gradient(circle at 50% 60%, ${p.primarySoft} 0%, ${p.primarySoft} 58%, transparent 62%)` }}>
              {sereniMotion === 'hop' && <span style={{ position: 'absolute', inset: 4, borderRadius: '50%', border: `2px solid ${p.primary}`, animation: 'amPing .9s ease-out 1', pointerEvents: 'none' }} />}
              <img src={`assets/turtle-${sereniPose}.png?v=3`} alt="" draggable={false} className="am-sereni-img"
                style={{ width: 56, height: 56, objectFit: 'contain', display: 'block', margin: '0 auto', filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.16))', pointerEvents: 'none' }} />
              {friendTyping && (
                <span aria-hidden="true" style={{ position: 'absolute', top: -4, left: -8, background: p.surface, border: `1px solid ${p.line}`, borderRadius: '12px 12px 12px 3px', padding: '3px 7px', fontSize: 9, color: p.inkSoft, display: 'inline-flex', gap: 3, alignItems: 'center', boxShadow: '0 2px 6px rgba(0,0,0,0.08)' }}>
                  {[0, 0.2, 0.4].map(d => <span key={d} style={{ width: 4, height: 4, borderRadius: '50%', background: p.inkSoft, display: 'inline-block', animation: `amTypingDot 1.2s ease-in-out ${d}s infinite` }} />)}
                </span>
              )}
            </button>
          </div>
        </div>
      </div>

      {reactError && (
        <div role="alert" style={{ padding: '8px 16px', background: 'rgba(197,82,75,0.1)', borderTop: '1px solid rgba(197,82,75,0.3)', fontSize: 12, color: AM_RED, flexShrink: 0 }}>{reactError}</div>
      )}

      {/* ── Compositor ── */}
      <div style={{ padding: '8px 12px calc(8px + max(8px, env(safe-area-inset-bottom, 0px)))', borderTop: `1px solid ${p.line}`, background: p.surface, flexShrink: 0 }}>
        <div style={{ maxWidth: 720, margin: '0 auto', display: 'flex', alignItems: 'flex-end', gap: 8 }}>
          <button onClick={handleLocation} disabled={locLoading} aria-label="Compartir ubicación" title="Compartir ubicación" className="am-press" style={{
            width: 42, height: 44, flexShrink: 0, borderRadius: 14, border: `1px solid ${p.line}`, background: p.bg, color: p.inkSoft, cursor: 'pointer', display: 'grid', placeItems: 'center',
          }}>
            {locLoading ? <span style={{ width: 14, height: 14, border: `2px solid ${p.line}`, borderTopColor: p.primaryDeep, borderRadius: '50%', animation: 'amSpin .8s linear infinite', display: 'block' }} /> : (
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22s7-7.1 7-12a7 7 0 1 0-14 0c0 4.9 7 12 7 12z" /><circle cx="12" cy="10" r="2.5" /></svg>
            )}
          </button>
          <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'flex-end', gap: 4, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 20, padding: '4px 6px 4px 14px', minHeight: 44, boxSizing: 'border-box', transition: 'border-color .2s' }}
            onFocusCapture={e => { e.currentTarget.style.borderColor = p.primary; }} onBlurCapture={e => { e.currentTarget.style.borderColor = p.line; }}>
            <textarea ref={inputRef} value={input} onChange={onInputChange} rows={1} className="am-textarea" aria-label="Mensaje"
              onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
              placeholder={`Escribe a ${firstName}…`} enterKeyHint="send"
              style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', background: 'transparent', color: p.ink, fontSize: 16, fontFamily: 'inherit', padding: '8px 0', maxHeight: 112, overflowY: 'auto' }} />
            {typeof VoiceButton === 'function' && <VoiceButton p={p} getValue={() => input} onFull={setInput} />}
          </div>
          <button onClick={send} disabled={!hasText} aria-label="Enviar" title="Enviar" className="am-press" style={{
            width: 44, height: 44, flexShrink: 0, borderRadius: 22, border: 'none', cursor: hasText ? 'pointer' : 'default',
            background: hasText ? p.primaryDeep : p.line, color: '#fff', display: 'grid', placeItems: 'center', transition: 'background .2s, transform .12s',
            boxShadow: hasText ? `0 4px 12px ${p.primaryDeep}55` : 'none',
          }}>
            {sending > 0 && !hasText ? (
              <span style={{ width: 16, height: 16, border: '2px solid rgba(255,255,255,0.5)', borderTopColor: '#fff', borderRadius: '50%', animation: 'amSpin .8s linear infinite', display: 'block' }} />
            ) : (
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 2L11 13" /><path d="M22 2l-7 20-4-9-9-4 20-7z" /></svg>
            )}
          </button>
        </div>
      </div>

      {/* ── Hoja de Sereni ── */}
      {sereniOpen && (
        <AmSheet p={p} onClose={closeSereni} zIndex={6000}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
            <Turtle pose={sereniPose} size={54} animate style={{ filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.14))' }} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 18, fontFamily: 'Fraunces, serif', fontWeight: 600 }}>Sereni</div>
              <div style={{ fontSize: 12.5, color: p.inkMute, lineHeight: 1.4 }}>
                {friendMood && ['triste', 'ansioso', 'cansado'].includes(friendMood.mood)
                  ? `${firstName} hoy se siente ${(AM_MOOD_LABEL[friendMood.mood] || friendMood.mood).toLowerCase()}. Un mensaje tuyo puede acompañar.`
                  : `Pequeñas ayudas para acompañar a ${firstName}.`}
              </div>
            </div>
          </div>

          {sereniView === 'menu' && (
            <div style={{ background: p.surface, border: `1px solid ${p.line}`, borderRadius: 16, overflow: 'hidden' }}>
              <button onClick={() => setSereniView('ideas')} style={menuRow()}>
                <span style={{ fontSize: 18, width: 24, textAlign: 'center' }}>💡</span>
                <span><div style={{ fontWeight: 600 }}>Ideas para escribir</div><div style={{ fontSize: 12, color: p.inkMute }}>Frases cálidas para empezar</div></span>
                <span style={{ marginLeft: 'auto', color: p.inkMute }}>›</span>
              </button>
              <button onClick={() => sereniSend(AM_MSG_PAUSA, `Le propusiste una pausa a ${firstName} 🌿`)} disabled={sereniBusy} style={menuRow()}>
                <span style={{ fontSize: 18, width: 24, textAlign: 'center' }}>🌿</span>
                <span><div style={{ fontWeight: 600 }}>Proponer una pausa juntos</div><div style={{ fontSize: 12, color: p.inkMute }}>Invita a respirar 2 minutos</div></span>
              </button>
              <button onClick={() => sereniSend(AM_MSG_ABRAZO, `Le mandaste un abrazo a ${firstName} 🐢`)} disabled={sereniBusy} style={menuRow()}>
                <span style={{ fontSize: 18, width: 24, textAlign: 'center' }}>🐢</span>
                <span><div style={{ fontWeight: 600 }}>Mandar un abrazo</div><div style={{ fontSize: 12, color: p.inkMute }}>Un mensaje corto que dice "aquí estoy"</div></span>
              </button>
              <button onClick={() => setSereniView('confirm-sos')} style={menuRow({ borderBottom: 'none' })}>
                <span style={{ fontSize: 18, width: 24, textAlign: 'center' }}>🤝</span>
                <span><div style={{ fontWeight: 600, color: AM_RED }}>Pedir compañía ahora</div><div style={{ fontSize: 12, color: p.inkMute }}>Avisa a {firstName} y a tus contactos cercanos</div></span>
                <span style={{ marginLeft: 'auto', color: p.inkMute }}>›</span>
              </button>
            </div>
          )}

          {sereniView === 'ideas' && (
            <div>
              <div style={{ fontSize: 11, fontWeight: 700, color: p.inkMute, letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 8 }}>Toca una para ponerla en tu mensaje</div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {amIdeasFor(friendMood?.mood).map(t => (
                  <button key={t} onClick={() => useIdea(t)} className="am-press" style={{ textAlign: 'left', padding: '12px 14px', borderRadius: 14, border: `1px solid ${p.line}`, background: p.surface, color: p.ink, fontSize: 14, lineHeight: 1.4, cursor: 'pointer', fontFamily: 'inherit' }}>{t}</button>
                ))}
              </div>
              <button onClick={() => setSereniView('menu')} style={{ ...amBtnGhost(p), marginTop: 12, width: '100%' }}>‹ Volver</button>
            </div>
          )}

          {sereniView === 'confirm-sos' && (
            <div style={{ padding: 14, borderRadius: 16, background: 'rgba(197,82,75,0.07)', border: '1px solid rgba(197,82,75,0.3)' }}>
              <div style={{ fontSize: 15, fontWeight: 600, color: p.ink }}>¿Pedir compañía ahora?</div>
              <div style={{ fontSize: 13, color: p.inkSoft, lineHeight: 1.55, marginTop: 6 }}>
                Se enviará un aviso 🆘 a <b>{firstName}</b>{isFavorite ? ' y a tus demás contactos cercanos (★)' : ' y a tus contactos cercanos (★)'} con este mensaje:
                <div style={{ marginTop: 8, padding: '8px 10px', background: p.bg, borderRadius: 10, fontStyle: 'italic', color: p.ink }}>"{AM_MSG_COMPANIA}"</div>
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
                <button onClick={sereniSOS} disabled={sereniBusy} className="am-press" style={{ ...amBtnPrimary(p, sereniBusy), background: sereniBusy ? p.line : AM_RED, flex: 1.3 }}>{sereniBusy ? 'Avisando…' : 'Sí, avisar'}</button>
                <button onClick={() => setSereniView('menu')} style={{ ...amBtnGhost(p), flex: 1 }}>Cancelar</button>
              </div>
            </div>
          )}
        </AmSheet>
      )}

      {/* ── Hoja de opciones del chat ── */}
      {showHeaderMenu && (
        <AmSheet p={p} onClose={() => { setShowHeaderMenu(false); setEditingNick(false); }} zIndex={6000}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
            <AmAvatar p={p} pose={friendMood ? amMoodPose(friendMood.mood, friend.pose) : (friend.pose || 'sit')} size={48} dotColor={moodColor} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 17, fontWeight: 600, fontFamily: 'Fraunces, serif', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{displayName}</div>
              <div style={{ fontSize: 12, color: p.inkMute }}>{nick ? friend.name + ' · ' : ''}{friend.userType || 'Usuario'}</div>
            </div>
            {friend.personalCode && <AmCodeChip p={p} code={friend.personalCode} size="sm" />}
          </div>
          {editingNick ? (
            <div style={{ marginBottom: 10 }}>
              <div style={{ fontSize: 11, color: p.inkSoft, marginBottom: 6, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em' }}>Apodo</div>
              <div style={{ display: 'flex', gap: 8 }}>
                <input autoFocus value={nickDraft} onChange={e => setNickDraft(e.target.value)} placeholder={friend.name}
                  onKeyDown={e => { if (e.key === 'Enter') { const n = nickDraft.trim(); if (userId) setNickname(userId, friend.id, n); setNick(n); setEditingNick(false); } if (e.key === 'Escape') setEditingNick(false); }}
                  style={{ ...amInput(p, false), flex: 1, width: 'auto' }} />
                <button onClick={() => { const n = nickDraft.trim(); if (userId) setNickname(userId, friend.id, n); setNick(n); setEditingNick(false); }} style={{ ...amBtnPrimary(p, false), padding: '11px 16px' }}>OK</button>
              </div>
            </div>
          ) : (
            <div style={{ background: p.surface, border: `1px solid ${p.line}`, borderRadius: 16, overflow: 'hidden' }}>
              {onOpenProfile && (
                <button onClick={() => { setShowHeaderMenu(false); onOpenProfile(friend); }} style={menuRow()}>
                  <span style={{ fontSize: 17, width: 22, textAlign: 'center' }}>✦</span><span>Ver muro</span><span style={{ marginLeft: 'auto', color: p.inkMute }}>›</span>
                </button>
              )}
              <button onClick={handleToggleFav} style={menuRow()}>
                <span style={{ fontSize: 17, width: 22, textAlign: 'center', color: isFavorite ? AM_STAR : p.inkMute }}>{isFavorite ? '★' : '☆'}</span>
                <span>{isFavorite ? 'Quitar de cercanos' : 'Marcar como contacto cercano'}</span>
              </button>
              <button onClick={() => { const next = !muted; if (userId) toggleMuteFriend(userId, friend.id); setMuted(next); }} style={menuRow()}>
                <span style={{ fontSize: 17, width: 22, textAlign: 'center' }}>{muted ? '🔕' : '🔔'}</span><span>{muted ? 'Quitar silencio' : 'Silenciar notificaciones'}</span>
              </button>
              <button onClick={() => { setNickDraft(nick); setEditingNick(true); }} style={menuRow({ borderBottom: 'none' })}>
                <span style={{ fontSize: 17, width: 22, textAlign: 'center' }}>✎</span><span>{nick ? 'Cambiar apodo' : 'Poner apodo'}</span>
                {nick && <span style={{ marginLeft: 'auto', color: p.inkMute, fontSize: 12 }}>{nick}</span>}
              </button>
            </div>
          )}
          <button onClick={() => { setShowHeaderMenu(false); setEditingNick(false); }} style={{ ...amBtnGhost(p), marginTop: 14, width: '100%' }}>Cerrar</button>
        </AmSheet>
      )}
    </div>
  );
}

// Tarjeta de ubicación (enlace de Google Maps) dentro de una burbuja
function AmMapCard({ p, isMe, text, match }) {
  const [, url, lat, lng] = match;
  const preText = text.replace(match[0], '').trim();
  const open = (e) => { e.stopPropagation(); window.open(url, '_blank', 'noopener,noreferrer'); };
  return (
    <>
      {preText && <div style={{ marginBottom: 8, whiteSpace: 'pre-wrap' }}>{preText}</div>}
      <div onClick={open} role="link" style={{ borderRadius: 12, overflow: 'hidden', margin: '2px -4px 0', border: `1px solid ${isMe ? 'rgba(255,255,255,0.18)' : p.line}`, cursor: 'pointer', background: isMe ? 'rgba(255,255,255,0.08)' : p.bg }}>
        <div style={{ padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{ fontSize: 22 }}>📍</span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: isMe ? '#fff' : p.ink }}>Mi ubicación actual</div>
            <div style={{ fontSize: 10.5, marginTop: 2, color: isMe ? 'rgba(255,255,255,0.75)' : p.inkMute, fontFamily: 'ui-monospace, Menlo, monospace' }}>{parseFloat(lat).toFixed(4)}, {parseFloat(lng).toFixed(4)}</div>
          </div>
          <span style={{ padding: '6px 11px', borderRadius: 999, background: isMe ? 'rgba(255,255,255,0.22)' : p.primaryDeep, color: '#fff', fontSize: 11, fontWeight: 600, whiteSpace: 'nowrap' }}>Abrir ↗</span>
        </div>
      </div>
    </>
  );
}

window.FriendChat = FriendChat;
