/* Overview news feed — the newest posts of the channel configured with the `news` role, shown to every
   signed-in user in a tall card beside the Overview widgets. The channel's own name is never shown. */

const { useState: useStateNews, useEffect: useEffectNews, useCallback: useCallbackNews, useRef: useRefNews } = React;

const NEWS_POLL_MS = 60 * 1000;

// What the AI layer calls a post. Trade calls never reach the card; "other" gets no chip.
const NEWS_LABELS = {
  news: ['News', 'var(--accent, #7aa7ff)'], stats: ['Stats', 'var(--bull)'],
  opinion: ['Opinion', 'var(--gold, #e0b050)'], promo: ['Promo', 'var(--text-4)'],
};

// "3m ago", "5h ago", else the date: the exact time is in the tooltip.
function newsAgo(iso, now) {
  const t = Date.parse(iso);
  if (!Number.isFinite(t)) return '';
  const s = Math.max(0, Math.round((now - t) / 1000));
  if (s < 60) return 'just now';
  if (s < 3600) return Math.floor(s / 60) + 'm ago';
  if (s < 86400) return Math.floor(s / 3600) + 'h ago';
  return new Date(t).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', timeZone: 'UTC' });
}

function newsDayLabel(iso, now) {
  const t = Date.parse(iso);
  if (!Number.isFinite(t)) return '';
  const day = (ms) => Math.floor(ms / 86400000);
  const d = day(now) - day(t);
  if (d === 0) return 'Today';
  if (d === 1) return 'Yesterday';
  return new Date(t).toLocaleDateString('en-GB', { weekday: 'short', day: '2-digit', month: 'short', timeZone: 'UTC' });
}

const NEWS_TOKEN_RE = /(https?:\/\/[^\s<>"']+|\$[A-Za-z][A-Za-z0-9]{1,9}\b|#[A-Za-z][A-Za-z0-9_]{1,24})/g;

// Plain text → React nodes: links become anchors, $TICKERs and #tags small chips. Everything else stays
// text, so nothing in a message can inject markup.
function renderNewsLine(line, keyBase) {
  return line.split(NEWS_TOKEN_RE).map((part, i) => {
    if (!part) return null;
    const key = keyBase + ':' + i;
    if (/^https?:\/\//.test(part)) {
      const m = part.match(/^(.*?)([.,;:!?)\]]*)$/);
      const url = m[1], tail = m[2];
      let host = url;
      try { host = new URL(url).hostname.replace(/^www\./, ''); } catch (_) { /* leave the raw text */ }
      return <React.Fragment key={key}>
        <a href={url} target="_blank" rel="noopener noreferrer nofollow" title={url}
           style={{ color: 'var(--accent, #7aa7ff)', textDecoration: 'none', borderBottom: '1px dotted currentColor' }}>{host}</a>{tail}
      </React.Fragment>;
    }
    if (part[0] === '$' || part[0] === '#') {
      const coin = part[0] === '$';
      return <span key={key} style={{
        display: 'inline-block', padding: '0 6px', margin: '0 1px', borderRadius: 999, fontSize: '0.92em', lineHeight: 1.5,
        fontFamily: 'var(--font-mono)', color: coin ? 'var(--gold, #e0b050)' : 'var(--text-3)',
        background: coin ? 'rgba(224,176,80,0.12)' : 'rgba(128,128,128,0.12)',
      }}>{part}</span>;
    }
    return part;
  });
}

function NewsItem({ item, now }) {
  const paragraphs = (item.text || '').split(/\n{2,}/).map(p => p.trim()).filter(Boolean);
  // A short first paragraph followed by more text reads as the headline.
  const headline = paragraphs.length > 1 && paragraphs[0].length <= 110 && !paragraphs[0].includes('\n') ? paragraphs[0] : null;
  const body = headline ? paragraphs.slice(1) : paragraphs;
  const exact = new Date(item.at).toLocaleString('en-GB', {
    timeZone: 'UTC', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', hour12: false }) + ' UTC';
  return (
    <article style={{
      padding: '12px 14px', borderRadius: 12, background: 'var(--bg-2, rgba(128,128,128,0.06))',
      border: '1px solid var(--border-soft)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
        <span title={exact} style={{ fontSize: 10.5, color: 'var(--text-4)', letterSpacing: 0.3, textTransform: 'uppercase' }}>
          {newsAgo(item.at, now)}
        </span>
        {NEWS_LABELS[item.label] && (
          <span title="Sorted by AI" style={{
            fontSize: 9.5, padding: '1px 7px', borderRadius: 999, letterSpacing: 0.4, textTransform: 'uppercase',
            color: NEWS_LABELS[item.label][1], border: '1px solid currentColor', opacity: 0.85 }}>
            {NEWS_LABELS[item.label][0]}
          </span>
        )}
      </div>
      {headline && <div style={{ fontSize: 14, fontWeight: 650, lineHeight: 1.35, color: 'var(--text-1, inherit)', marginBottom: 6 }}>
        {renderNewsLine(headline, 'h')}
      </div>}
      {body.map((p, i) => (
        <p key={i} style={{ margin: i ? '8px 0 0' : 0, fontSize: 13, lineHeight: 1.5, color: 'var(--text-2)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
          {renderNewsLine(p, 'p' + i)}
        </p>
      ))}
      {item.media.map((m, i) => (
        <a key={i} href={m.url} target="_blank" rel="noopener noreferrer">
          <img src={m.url} alt="" loading="lazy" style={{
            display: 'block', width: '100%', marginTop: 10, borderRadius: 10, border: '1px solid var(--border-soft)',
            maxHeight: 320, objectFit: 'cover' }} />
        </a>
      ))}
    </article>
  );
}

function W_NewsFeed() {
  const [head, setHead] = useStateNews(null);         // newest page, replaced on every refresh; null = loading
  const [tail, setTail] = useStateNews([]);           // older pages the reader asked for
  const [error, setError] = useStateNews(false);
  const [cursor, setCursor] = useStateNews(null);     // raw id to page back from; null = nothing older
  const [loadingMore, setLoadingMore] = useStateNews(false);
  const [now, setNow] = useStateNews(Date.now());
  const alive = useRefNews(true);
  const firstLoad = useRefNews(true);
  // The AI switch is for admins: the endpoint refuses everyone else, and then no switch is drawn.
  const [ai, setAi] = useStateNews(null);
  const [aiBusy, setAiBusy] = useStateNews(false);

  useEffectNews(() => {
    fetch('/api/news/ai').then(x => x.json()).then(r => { if (alive.current && r && r.ok) setAi(r.data); }).catch(() => {});
  }, []);

  async function toggleAi() {
    if (!ai || aiBusy) return;
    setAiBusy(true);
    try {
      const r = await fetch('/api/news/ai', {
        method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !ai.enabled }),
      }).then(x => x.json());
      if (r && r.ok) setAi(r.data);
    } catch (_) { /* the switch stays as it was */ }
    setAiBusy(false);
  }

  const load = useCallbackNews(async () => {
    try {
      const r = await fetch('/api/news?limit=60').then(x => x.json());
      if (!alive.current) return;
      if (!r || !r.ok) throw new Error('bad response');
      setHead(r.data);
      if (firstLoad.current) { firstLoad.current = false; setCursor(r.next_before_id); }
      setError(false);
      setNow(Date.now());
    } catch (_) {
      if (alive.current) setError(true);
    }
  }, []);

  useEffectNews(() => {
    alive.current = true;
    load();
    const t = setInterval(() => { if (!document.hidden) load(); }, NEWS_POLL_MS);
    return () => { alive.current = false; clearInterval(t); };
  }, []);

  async function more() {
    if (!cursor || loadingMore) return;
    setLoadingMore(true);
    try {
      const r = await fetch('/api/news?limit=60&before_id=' + cursor).then(x => x.json());
      if (r && r.ok) { setTail(prev => [...prev, ...r.data]); setCursor(r.next_before_id); }
    } catch (_) { /* the button stays; try again */ }
    setLoadingMore(false);
  }

  const seen = new Set((head || []).map(i => i.id));
  const items = head === null ? null : [...head, ...tail.filter(i => !seen.has(i.id))];

  // Day separators between items.
  const rows = [];
  let lastDay = null;
  (items || []).forEach(it => {
    const d = newsDayLabel(it.at, now);
    if (d !== lastDay) { rows.push(<div key={'d' + it.id} style={{
      position: 'sticky', top: 0, zIndex: 1, padding: '6px 2px', fontSize: 10.5, letterSpacing: 0.6, textTransform: 'uppercase',
      color: 'var(--text-4)', background: 'var(--bg-card)' }}>{d}</div>); lastDay = d; }
    rows.push(<NewsItem key={it.id} item={it} now={now} />);
  });

  return (
    <div className="card ov-news" style={{ display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
      <div className="card-h" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <span style={{ fontWeight: 600, fontSize: 13 }}>Market news</span>
        <span title="Updates every minute" style={{ width: 7, height: 7, borderRadius: 99, background: error ? 'var(--bear)' : 'var(--bull)', opacity: 0.85 }} />
        {ai && (
          <button onClick={toggleAi} disabled={aiBusy}
            title={ai.enabled
              ? `AI sorting is on: it labels posts and joins split ones (${ai.used_today} of ${ai.daily_cap} calls today). Click to turn it off.`
              : 'AI sorting is off: posts are grouped by time only. Click to turn it on.'}
            style={{
              marginLeft: 'auto', padding: '2px 9px', borderRadius: 999, cursor: 'pointer', fontSize: 10.5, letterSpacing: 0.3,
              background: 'transparent', border: '1px solid currentColor', color: ai.enabled ? 'var(--bull)' : 'var(--text-4)' }}>
            AI {ai.enabled ? 'on' : 'off'}
          </button>
        )}
      </div>
      <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '0 14px 14px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        {items === null && !error && <div style={{ color: 'var(--text-4)', fontSize: 12, padding: '18px 2px' }}>Loading…</div>}
        {items === null && error && <div style={{ color: 'var(--text-4)', fontSize: 12, padding: '18px 2px' }}>News is unavailable right now.</div>}
        {items && !items.length && <div style={{ color: 'var(--text-4)', fontSize: 12, padding: '18px 2px' }}>No news yet.</div>}
        {rows}
        {cursor && items && items.length > 0 && (
          <button onClick={more} disabled={loadingMore} style={{
            alignSelf: 'center', margin: '4px 0', padding: '5px 14px', borderRadius: 999, cursor: 'pointer', fontSize: 11,
            background: 'transparent', border: '1px solid var(--border-soft)', color: 'var(--text-3)' }}>
            {loadingMore ? 'Loading…' : 'Show earlier'}
          </button>
        )}
      </div>
    </div>
  );
}

window.W_NewsFeed = W_NewsFeed;
