// ---- Blog data source --------------------------------------------------------------------------
// For now posts are read live from the current WordPress site. After the posts are migrated, replace
// listPosts and getPost below; the pages only rely on the shape they return:
//   { slug, title, excerpt, date, topic, image, html?, words? }
// Same-site address: vercel.json passes /wp-json/* through to the WordPress server.
const BLOG_API = '/wp-json/wp/v2';
const BLOG_PER_PAGE = 9;
// WordPress categories folded into a few topics; anything else counts as General
const BLOG_TOPICS = [{ key: 'all', label: 'All articles' }, { key: 'comms', label: 'Business communications', cats: [8] }, { key: 'voip', label: 'VoIP and carriers', cats: [7] }, { key: 'marketing', label: 'Marketing', cats: [10, 11, 12, 13, 14] }];
const topicOf = cats => (BLOG_TOPICS.find(t => t.cats && t.cats.some(c => cats.includes(c))) || { label: 'General' }).label;
const decode = s => { const d = document.createElement('textarea'); d.innerHTML = s || ''; return d.value; };
const stripTags = s => decode((s || '').replace(/<[^>]+>/g, ' ')).replace(/\s+/g, ' ').trim();
const blogCache = new Map();
const cached = (key, fn) => { if (!blogCache.has(key)) blogCache.set(key, fn().catch(e => { blogCache.delete(key); throw e; })); return blogCache.get(key); };
const toPost = p => ({ slug: p.slug, title: decode(p.title.rendered), excerpt: stripTags(p.excerpt && p.excerpt.rendered).replace(/\s*\[(…|&hellip;|\.\.\.)\]\s*$/, '…'), date: p.date, topic: topicOf(p.categories || []), cats: p.categories || [],
  image: p._embedded && p._embedded['wp:featuredmedia'] && p._embedded['wp:featuredmedia'][0] && p._embedded['wp:featuredmedia'][0].source_url || '',
  html: p.content && p.content.rendered, author: p._embedded && p._embedded.author && p._embedded.author[0] && p._embedded.author[0].name });
function listPosts({ page = 1, topic = 'all', search = '' }) {
  const t = BLOG_TOPICS.find(x => x.key === topic); const q = new URLSearchParams({ per_page: BLOG_PER_PAGE, page, _embed: 'wp:featuredmedia', _fields: 'slug,title,excerpt,date,categories,_links,_embedded' });
  if (t && t.cats) q.set('categories', t.cats.join(',')); if (search) q.set('search', search);
  return cached('list:' + q, async () => { const r = await fetch(BLOG_API + '/posts?' + q); if (!r.ok) throw new Error('HTTP ' + r.status); return { posts: (await r.json()).map(toPost), total: +r.headers.get('X-WP-Total') || 0 }; });
}
function getPost(slug) {
  return cached('post:' + slug, async () => { const r = await fetch(BLOG_API + '/posts?' + new URLSearchParams({ slug, _embed: 'wp:featuredmedia,author' })); if (!r.ok) throw new Error('HTTP ' + r.status); const [p] = await r.json(); return p ? toPost(p) : null; });
}
// ------------------------------------------------------------------------------------------------

const fmtDate = d => new Date(d).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
const readMins = html => Math.max(1, Math.round(stripTags(html).split(' ').length / 230));
// Drop scripts, inline handlers and iframes from post HTML before rendering it
function cleanHtml(html) {
  const doc = new DOMParser().parseFromString(html || '', 'text/html');
  doc.querySelectorAll('script,style,iframe,object,embed,form').forEach(n => n.remove());
  doc.querySelectorAll('*').forEach(el => [...el.attributes].forEach(a => { if (/^on/i.test(a.name) || /^\s*javascript:/i.test(a.value)) el.removeAttribute(a.name); }));
  doc.querySelectorAll('h2').forEach((h, n) => { h.id = 'h-' + (n + 1); });
  doc.querySelectorAll('img').forEach(i => { if (/^data:/.test(i.getAttribute('srcset') || '')) { const real = i.getAttribute('data-srcset'); if (real) i.setAttribute('srcset', real); else i.removeAttribute('srcset'); } i.loading = 'lazy'; i.removeAttribute('width'); i.removeAttribute('height'); });
  return doc.body.innerHTML;
}
// Links to other posts on the old site open inside the new blog
const POST_LINK = /^https?:\/\/(www\.)?onecloudnetworks\.com\/([a-z0-9-]+)\/?$/i;

function PostThumb({ post, big }) {
  return post.image ? <img className="bl-img" src={post.image} alt="" loading={big ? 'eager' : 'lazy'} /> : <span className="bl-img bl-img-empty" aria-hidden="true"><DS.Icon name="PhoneCall" size={big ? 40 : 28} /></span>;
}
function PostCard({ post, go, n }) {
  return <a className="bl-card reveal" href={pathFor('post', { slug: post.slug })} onClick={e => { e.preventDefault(); go('post', { slug: post.slug }); }} style={{ '--stagger': (n % 3) * 60 + 'ms' }}>
    <span className="bl-media"><PostThumb post={post} /></span>
    <span className="bl-card-body"><span className="bl-topic">{post.topic}</span><span className="bl-title">{post.title}</span><span className="bl-excerpt">{post.excerpt}</span><span className="bl-date">{fmtDate(post.date)}</span></span>
  </a>;
}

// 1 2 3 … 12 style pagination: always the first and last page, plus the current page and its neighbours
function Pager({ page, count, onPage }) {
  const nums = [...new Set([1, count, page - 1, page, page + 1].filter(n => n >= 1 && n <= count))].sort((a, b) => a - b);
  const items = nums.flatMap((n, i) => i && n - nums[i - 1] > 1 ? ['gap' + n, n] : [n]);
  return <nav className="bl-pager" aria-label="Blog pages">
    <button type="button" className="bl-pg-step" onClick={() => onPage(page - 1)} disabled={page === 1}><DS.Icon name="ArrowLeft" size={16} /> Previous</button>
    <ol>{items.map(n => typeof n === 'string' ? <li key={n} className="bl-pg-gap" aria-hidden="true">…</li> : <li key={n}><button type="button" aria-current={n === page ? 'page' : undefined} aria-label={'Page ' + n} onClick={() => onPage(n)}>{n}</button></li>)}</ol>
    <button type="button" className="bl-pg-step" onClick={() => onPage(page + 1)} disabled={page === count}>Next <DS.Icon name="ArrowRight" size={16} /></button>
  </nav>;
}

function BlogScreen({ go, onContact }) {
  const [topic, setTopic] = React.useState('all'), [q, setQ] = React.useState(''), [search, setSearch] = React.useState('');
  const [page, setPage] = React.useState(() => Math.max(1, +new URLSearchParams(location.search).get('page') || 1)), [posts, setPosts] = React.useState(null), [total, setTotal] = React.useState(0), [err, setErr] = React.useState(false), [busy, setBusy] = React.useState(false), [tries, setTries] = React.useState(0);
  // Debounce the search box
  React.useEffect(() => { const t = setTimeout(() => setSearch(q.trim()), 350); return () => clearTimeout(t); }, [q]);
  const first = React.useRef(true);
  React.useEffect(() => { if (first.current) { first.current = false; return; } setPage(1); }, [topic, search]);
  // Keep the page number in the address so a link to page 3 opens page 3
  React.useEffect(() => { history.replaceState(null, '', pathFor('blog') + (page > 1 ? '?page=' + page : '')); }, [page]);
  React.useEffect(() => {
    let live = true; setBusy(true); setErr(false); setPosts(null);
    listPosts({ page, topic, search })
      .then(r => { if (!live) return; setPosts(r.posts); setTotal(r.total); setBusy(false); requestAnimationFrame(() => window.ocReveal && window.ocReveal()); })
      .catch(() => { if (live) { setErr(true); setBusy(false); } });
    return () => { live = false; };
  }, [topic, search, page, tries]);
  const filtered = topic !== 'all' || search, pageCount = Math.max(1, Math.ceil(total / BLOG_PER_PAGE));
  const showFeature = !filtered && page === 1;
  const [featured, ...rest] = posts || [];
  const listRef = React.useRef(null);
  const toPage = n => { if (n < 1 || n > pageCount || n === page) return; setPage(n); const el = listRef.current; if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 96, behavior: 'instant' }); };
  const active = BLOG_TOPICS.find(t => t.key === topic);
  // Posts often carry several categories; under a topic filter, label cards with that topic
  const grid = (showFeature ? rest : posts || []).map(p => topic !== 'all' ? { ...p, topic: active.label } : p);
  return <>
    <section className="bl-head"><Container><div className="reveal"><p className="caption">Blog</p><h1>Guides for businesses that can't miss a call.</h1><p className="lead">Plain-English answers on AI receptionists, business phone systems, internet and carrier networks, from our team in {HQ}, Texas.</p></div>
      <div className="bl-tools reveal"><div className="bl-topics" role="group" aria-label="Filter by topic">{BLOG_TOPICS.map(t => <button key={t.key} type="button" aria-pressed={topic === t.key} onClick={() => setTopic(t.key)}>{t.label}</button>)}</div>
        <label className="bl-search"><DS.Icon name="Search" size={18} /><input type="search" placeholder="Search articles" value={q} onChange={e => setQ(e.target.value)} aria-label="Search articles" /></label></div>
    </Container></section>
    <Section labelledBy={undefined}><div ref={listRef} />
      {err && <div className="bl-empty" role="alert"><h2>We couldn't load the articles.</h2><p>Check your connection and try again.</p><DS.Button variant="secondary" onClick={() => { blogCache.clear(); setTries(t => t + 1); }}>Try again</DS.Button></div>}
      {!err && !posts && <div className="bl-grid" aria-busy="true" aria-label="Loading articles">{Array.from({ length: 6 }, (_, i) => <div key={i} className="bl-skel"><span /><i /><i /><i /></div>)}</div>}
      {!err && posts && !posts.length && <div className="bl-empty"><h2>No articles match{search ? ' "' + search + '"' : ''}.</h2><p>Try a different word or topic.</p></div>}
      {!err && posts && posts.length > 0 && <>
        {showFeature && featured && <a className="bl-feature reveal" href={pathFor('post', { slug: featured.slug })} onClick={e => { e.preventDefault(); go('post', { slug: featured.slug }); }}>
          <span className="bl-media"><PostThumb post={featured} big /></span>
          <span className="bl-card-body"><span className="bl-topic">Latest · {featured.topic}</span><span className="bl-title">{featured.title}</span><span className="bl-excerpt">{featured.excerpt}</span><span className="bl-more">Read the article <DS.Icon name="ArrowRight" size={16} /></span><span className="bl-date">{fmtDate(featured.date)}</span></span>
        </a>}
        <div className="bl-grid">{grid.map((p, n) => <PostCard key={p.slug} post={p} go={go} n={n} />)}</div>
        {pageCount > 1 && <Pager page={page} count={pageCount} onPage={toPage} />}
      </>}
    </Section>
  </>;
}

function PostScreen({ slug, go, onContact }) {
  const [post, setPost] = React.useState(undefined), [related, setRelated] = React.useState([]), [cur, setCur] = React.useState('');
  React.useEffect(() => { let live = true; getPost(slug).then(p => { if (!live) return; setPost(p); if (p) { document.title = p.title + ' | OneCloud Networks'; if (p.excerpt) document.querySelector('meta[name="description"]').setAttribute('content', p.excerpt.slice(0, 160)); } }).catch(() => live && setPost(null)); return () => { live = false; }; }, [slug]);
  React.useEffect(() => { if (!post) return; const t = BLOG_TOPICS.find(x => x.cats && x.cats.some(c => post.cats.includes(c))); listPosts({ topic: t ? t.key : 'all' }).then(r => setRelated(r.posts.filter(p => p.slug !== post.slug).slice(0, 3))).catch(() => {}); requestAnimationFrame(() => window.ocReveal && window.ocReveal()); }, [post]);
  const html = React.useMemo(() => post ? cleanHtml(post.html) : '', [post]);
  const toc = React.useMemo(() => { const d = new DOMParser().parseFromString(html, 'text/html'); return [...d.querySelectorAll('h2')].map(h => [h.id, h.textContent.trim()]).filter(([, t]) => t); }, [html]);
  // Highlight the contents entry for the heading being read
  React.useEffect(() => { if (!toc.length) return; const seen = new Set(); const io = new IntersectionObserver(es => { es.forEach(e => e.isIntersecting ? seen.add(e.target.id) : seen.delete(e.target.id)); const f = toc.find(([id]) => seen.has(id)); if (f) setCur(f[0]); }, { rootMargin: '-96px 0px -60% 0px' }); toc.forEach(([id]) => { const el = document.getElementById(id); el && io.observe(el); }); return () => io.disconnect(); }, [toc]);
  const onArticleClick = e => { const a = e.target.closest('a'); if (!a) return; const m = POST_LINK.exec(a.href); if (m) { e.preventDefault(); const r = resolvePath('/' + m[2] + '/'); go(r.route, { slug: r.slug }); } else if (/^https?:/.test(a.href) && !a.href.startsWith(location.origin)) { a.target = '_blank'; a.rel = 'noopener'; } };
  const jump = (e, id) => { e.preventDefault(); const el = document.getElementById(id); if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 96, behavior: 'smooth' }); };
  const back = <a className="bl-back" href={pathFor('blog')} onClick={e => { e.preventDefault(); go('blog'); }}><DS.Icon name="ArrowLeft" size={16} /> All articles</a>;
  if (post === undefined) return <Container><div className="bp-loading" aria-busy="true">{back}<div className="bl-skel bl-skel-post"><i /><i /><span /></div></div></Container>;
  if (post === null) return <Container><div className="bl-empty" style={{ padding: '96px 0' }}>{back}<h1>We couldn't find that article.</h1><p>It may have moved. <a href={pathFor('blog')} onClick={e => { e.preventDefault(); go('blog'); }}>Browse all articles</a> or call us and we'll help you find it.</p></div></Container>;
  return <>
    <article className="bp">
      <header className="bp-head"><Container><div className="bp-head-in reveal">{back}<p className="bl-topic">{post.topic}</p><h1>{post.title}</h1><p className="bp-meta"><span>{fmtDate(post.date)}</span><span>{readMins(post.html)} min read</span><span>By {!post.author || /admin/i.test(post.author) ? 'the OneCloud team' : post.author}</span></p></div>
        {post.image && <div className="bp-hero reveal"><img src={post.image} alt="" /></div>}</Container></header>
      <Container><div className="bp-layout">
        <div className="bp-body prose" onClick={onArticleClick} dangerouslySetInnerHTML={{ __html: html }} />
        <aside className="bp-side">
          {toc.length > 1 && <nav className="legal-toc bp-toc" aria-label="In this article"><p className="caption">In this article</p><ol>{toc.map(([id, t], n) => <li key={id}><a href={'#' + id} aria-current={cur === id ? 'true' : undefined} onClick={e => jump(e, id)}><span className="num">{n + 1}.</span>{t}</a></li>)}</ol></nav>}
          <div className="bp-cta on-navy"><p className="caption">Hear it live</p><p className="bp-cta-t">Call the demo line and talk to the AI receptionist yourself.</p><CallButton full /><DS.Button variant="secondary" onDark fullWidth onClick={onContact}>Book a free demo</DS.Button></div>
        </aside>
      </div></Container>
    </article>
    {related.length > 0 && <Section tone="subtle" labelledBy="rel-title"><Head id="rel-title" title="Keep reading" /><div className="bl-grid">{related.map((p, n) => <PostCard key={p.slug} post={p} go={go} n={n} />)}</div></Section>}
  </>;
}
Object.assign(window, { BlogScreen, PostScreen });
