// src/features/messages/ui/thread-view.jsx
//
// Scrollable thread of message bubbles. Newest at the bottom. Scroll-up
// triggers loadMessages(before=oldest.created_at) to lazy-load older history.
// Subscribes to realtime INSERT+UPDATE for this conversation.
//
// Pinned messages:
//   - When at least one message has pinned_at != null, a sticky bar at the
//     top of the thread shows their compact previews (up to 3).
//   - Clicking a pinned preview scrolls the original message into view.
//   - Toggling pin is exposed to MessageBubble via window.MessagePinController,
//     which optimistically flips pinned_at and preserves scroll position so
//     the bar growing/shrinking doesn't yank the user's view.

const TV_pinnedExcerpt = (m) => {
  const body = (m.body || '').replace(/\s+/g, ' ').trim();
  if (body) return body.length > 80 ? body.slice(0, 80) + '…' : body;
  if (m.has_attachments) return '[Attachment]';
  return '—';
};

const ThreadView = ({ conversationId, viewerUserId, peerLabel, readOnly }) => {
  const [messages, setMessages] = React.useState([]);
  const [loading,  setLoading]  = React.useState(true);
  const [loadingOlder, setLoadingOlder] = React.useState(false);
  const [hasMore,  setHasMore]  = React.useState(true);
  const [err,      setErr]      = React.useState('');
  const scrollRef = React.useRef(null);
  const bubbleRefs = React.useRef({});

  const scrollToBottom = (smooth = false) => {
    const el = scrollRef.current;
    if (!el) return;
    el.scrollTo({ top: el.scrollHeight, behavior: smooth ? 'smooth' : 'auto' });
  };

  // Preserve scroll position relative to the bottom across height changes
  // (used when the pinned bar grows or shrinks).
  const preserveScroll = React.useCallback((mutate) => {
    const el = scrollRef.current;
    if (!el) { mutate(); return; }
    const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
    mutate();
    requestAnimationFrame(() => {
      const el2 = scrollRef.current;
      if (!el2) return;
      el2.scrollTop = el2.scrollHeight - el2.clientHeight - distanceFromBottom;
    });
  }, []);

  // Initial load + on conversation switch.
  React.useEffect(() => {
    let cancel = false;
    setLoading(true); setErr(''); setHasMore(true); setMessages([]);
    (async () => {
      try {
        const rows = await window.Messages.db.loadMessages(conversationId, { limit: 50 });
        if (cancel) return;
        setMessages(rows);
        setHasMore(rows.length >= 50);
        setLoading(false);
        setTimeout(() => scrollToBottom(false), 0);
        if (!readOnly) {
          try { await window.Messages.db.markRead(conversationId); } catch (e) {}
        }
      } catch (e) {
        if (!cancel) { setErr(e.message || String(e)); setLoading(false); }
      }
    })();
    return () => { cancel = true; };
  }, [conversationId, readOnly]);

  // Realtime subscription for this conversation.
  React.useEffect(() => {
    const sub = window.Messages.db.subscribeToConversation(conversationId, (evt) => {
      if (evt.type === 'insert') {
        const row = evt.row;
        (async () => {
          try {
            const recent = await window.Messages.db.loadMessages(conversationId, { limit: 1 });
            if (recent.length && recent[recent.length - 1].id === row.id) {
              setMessages(prev => {
                if (prev.some(m => m.id === row.id)) return prev;
                return [...prev, recent[recent.length - 1]];
              });
              setTimeout(() => scrollToBottom(true), 50);
              if (!readOnly && row.sender_id !== viewerUserId) {
                try { await window.Messages.db.markRead(conversationId); } catch (e) {}
              }
            }
          } catch (e) { /* swallow */ }
        })();
      } else if (evt.type === 'update') {
        const row = evt.row;
        preserveScroll(() => {
          setMessages(prev => prev.map(m => m.id === row.id
            ? { ...m, read_at: row.read_at, pinned_at: row.pinned_at }
            : m));
        });
      }
    });
    return () => sub.unsubscribe();
  }, [conversationId, viewerUserId, readOnly, preserveScroll]);

  // Expose a pin controller so MessageBubble (and the pinned bar) can drive
  // optimistic UI without prop-drilling.
  React.useEffect(() => {
    window.MessagePinController = {
      toggle: async (messageId, pinned) => {
        const before = messages.find(m => m.id === messageId);
        preserveScroll(() => {
          setMessages(prev => prev.map(m => m.id === messageId
            ? { ...m, pinned_at: pinned ? (new Date().toISOString()) : null }
            : m));
        });
        try {
          await window.Messages.db.togglePin(messageId, pinned);
        } catch (e) {
          // Roll back on failure.
          preserveScroll(() => {
            setMessages(prev => prev.map(m => m.id === messageId
              ? { ...m, pinned_at: before ? before.pinned_at : null }
              : m));
          });
          throw e;
        }
      },
    };
    return () => { if (window.MessagePinController) window.MessagePinController = null; };
  }, [messages, preserveScroll]);

  // Scroll-up to lazy-load older.
  const onScroll = async (e) => {
    if (loadingOlder || !hasMore || loading) return;
    if (e.target.scrollTop > 60) return;
    setLoadingOlder(true);
    const oldest = messages[0];
    const prevHeight = scrollRef.current?.scrollHeight || 0;
    try {
      const older = await window.Messages.db.loadMessages(conversationId, {
        before: oldest?.created_at, limit: 50,
      });
      if (older.length === 0) {
        setHasMore(false);
      } else {
        setMessages(prev => [...older, ...prev]);
        requestAnimationFrame(() => {
          const el = scrollRef.current;
          if (el) el.scrollTop = el.scrollHeight - prevHeight;
        });
      }
    } catch (e) { setErr(e.message || String(e)); }
    finally { setLoadingOlder(false); }
  };

  const scrollToMessage = (id) => {
    const node = bubbleRefs.current[id];
    const container = scrollRef.current;
    if (!node || !container) return;
    const top = node.offsetTop - 8;
    container.scrollTo({ top, behavior: 'smooth' });
    const original = node.style.boxShadow;
    node.style.boxShadow = '0 0 0 2px oklch(75% 0.14 80)';
    setTimeout(() => { node.style.boxShadow = original; }, 1200);
  };

  if (loading) {
    return <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center',
                          color:'oklch(56% 0.03 265)', fontSize:13 }}>Loading…</div>;
  }
  if (err) {
    return <div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center',
                          color:'oklch(40% 0.15 25)', fontSize:13, padding:24, textAlign:'center' }}>{err}</div>;
  }

  const pinned = messages.filter(m => m.pinned_at)
                         .sort((a, b) => new Date(b.pinned_at) - new Date(a.pinned_at));

  return (
    <div style={{ flex:1, display:'flex', flexDirection:'column', minHeight:0 }}>
      {pinned.length > 0 && (
        <div style={{ borderBottom:'1px solid oklch(92% 0.01 265)',
                       background:'oklch(98% 0.03 90)',
                       padding:'6px 12px',
                       fontFamily:"'Plus Jakarta Sans', sans-serif" }}>
          <div style={{ fontSize:10, fontWeight:700, letterSpacing:'0.08em',
                         textTransform:'uppercase', color:'oklch(45% 0.12 80)',
                         padding:'2px 4px 4px' }}>
            Pinned · {pinned.length}/3
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap:4 }}>
            {pinned.map(m => (
              <button key={m.id} type="button"
                onClick={() => scrollToMessage(m.id)}
                style={{ display:'flex', gap:8, alignItems:'center',
                         padding:'8px 10px', borderRadius:6,
                         border:'1px solid oklch(94% 0.04 90)', background:'#fff',
                         cursor:'pointer', fontFamily:'inherit', textAlign:'left',
                         width:'100%' }}>
                <span style={{ flex:1, fontSize:12, color:'oklch(28% 0.04 265)',
                                overflow:'hidden', textOverflow:'ellipsis',
                                whiteSpace:'nowrap', minWidth:0 }}>
                  {TV_pinnedExcerpt(m)}
                </span>
                {!readOnly && (
                  <span onClick={(e) => {
                    e.stopPropagation();
                    if (window.MessagePinController) {
                      window.MessagePinController.toggle(m.id, false).catch(err => alert(err.message || 'Could not unpin.'));
                    }
                  }}
                  style={{ fontSize:11, color:'oklch(50% 0.03 265)',
                           padding:'2px 6px', borderRadius:4,
                           background:'oklch(96% 0.005 60)' }}>
                    Unpin
                  </span>
                )}
              </button>
            ))}
          </div>
        </div>
      )}
      <div ref={scrollRef} onScroll={onScroll}
        style={{ flex:1, overflowY:'auto', padding:'14px 16px',
                  background:'oklch(98.5% 0.007 60)' }}>
        {loadingOlder && (
          <div style={{ textAlign:'center', fontSize:11, color:'oklch(58% 0.03 265)', padding:'6px 0' }}>Loading older…</div>
        )}
        {!hasMore && messages.length > 0 && (
          <div style={{ textAlign:'center', fontSize:11, color:'oklch(58% 0.03 265)',
                         padding:'6px 0 12px', fontStyle:'italic' }}>Beginning of conversation</div>
        )}
        {messages.length === 0 && (
          <div style={{ textAlign:'center', fontSize:13, color:'oklch(56% 0.03 265)',
                         padding:'40px 24px' }}>
            No messages yet. Say hello to {peerLabel || 'your coach'}.
          </div>
        )}
        {messages.map(m => (
          <window.MessageBubble key={m.id} message={m} isMine={m.sender_id === viewerUserId}
            anchorRef={(el) => { if (el) bubbleRefs.current[m.id] = el; else delete bubbleRefs.current[m.id]; }} />
        ))}
      </div>
    </div>
  );
};

window.Messages.ui.ThreadView = ThreadView;
window.ThreadView             = ThreadView;
