// Updates: post index + native post renderer.
// Post content lives in data/posts/<slug>.json; charts are computed at render
// time from data/questions.json so the figures are always the corrected ones.

function uSpecRows(spec, data) {
  const ci = uCI(data), ns = uN(data), countries = data.questions.countries;
  const item = UQ(data, spec.q);
  if (!item) return { rows: [], missing: spec.q };
  const m = uMatcher(spec.match);
  let rows;
  if (spec.kind === 'meanRank') rows = countries.map(c => ({ c, v: uMean(item, ci, c) }));
  else rows = countries.map(c => ({ c, v: uVal(item, ci, c, m) }));
  rows = rows.filter(r => r.v != null);
  rows.sort((a, b) => spec.asc ? a.v - b.v : b.v - a.v);
  if (spec.top) rows = rows.slice(0, spec.top);
  if (spec.highlight) rows.forEach(r => { if (spec.highlight.includes(r.c)) r.hl = true; });
  const glob = spec.kind === 'meanRank' ? null : uGlobal(item, ci, ns, countries, m);
  return { rows, glob, item };
}

function UChart({ spec: rawSpec, data }) {
  const spec = rawSpec.kindMean ? Object.assign({}, rawSpec, { kind: 'meanRank' }) : rawSpec;
  const ci = uCI(data), ns = uN(data), countries = data.questions.countries;
  const fmtMean = spec.scale ? (v => v == null ? '—' : v.toFixed(2) + '/' + spec.scale) : null;
  const foot = [spec.footnote, spec.source && ('Source: ' + spec.source)].filter(Boolean).join(' · ');
  const anyLow = (spec.kind !== 'answers') && countries.some(c => LOW_SAMPLE[c]);
  const lowNote = anyLow ? ' † Lebanon has a low sample size; interpret with caution.' : '';

  if (spec.kind === 'answers') {
    if (spec.items) {
      const m = uMatcher(spec.match);
      let rows = spec.items.map(it => {
        const item = UQ(data, it.q);
        return item ? { l: it.label, v: uGlobal(item, ci, ns, countries, m) } : null;
      }).filter(r => r && r.v != null);
      if (spec.sort !== false) rows.sort((a, b) => b.v - a.v);
      return (
        <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + (spec.slug || 'answers') + '.png'}>
          <UAnswerBars rows={rows} colors={spec.colors} accent={spec.accent} />
        </UChartCard>);
    }
    const item = UQ(data, spec.q);
    if (!item) return null;
    let rows = item.answers
      .filter(a => !(spec.exclude || []).some(x => a.label.toLowerCase().indexOf(x.toLowerCase()) >= 0))
      .map(a => ({ l: (spec.shorten ? a.label.replace(/\s*\(.*$/, '') : a.label), v: uGlobal(item, ci, ns, countries, l => l === a.label) }));
    if (spec.sort !== false) rows.sort((a, b) => b.v - a.v);
    if (spec.highlight) rows.forEach(r => { if (spec.highlight.some(h => r.l.toLowerCase().indexOf(h.toLowerCase()) >= 0)) r.hl = true; });
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + spec.q.toLowerCase() + '.png'}>
        <UAnswerBars rows={rows} colors={spec.colors} accent={spec.accent} />
      </UChartCard>);
  }

  if (spec.kind === 'bands') {
    const rows = spec.items.map(it => {
      const item = UQ(data, it.q);
      if (!item) return null;
      return Object.assign({ l: it.label }, uBandsOf(item, ci, ns, countries, spec.bands));
    }).filter(Boolean);
    if (!rows.length) return null;
    if (spec.sort !== false) rows.sort((a, b) => b.agree - a.agree);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + (spec.slug || 'bands') + '.png'}>
        <UBands rows={rows} vertical={spec.vertical} defs={spec.bands} />
      </UChartCard>);
  }

  if (spec.kind === 'lines') {
    const ia = UQ(data, spec.qa), ib = UQ(data, spec.qb);
    if (!ia || !ib) return null;
    const ma = uMatcher(spec.matchA), mb = uMatcher(spec.matchB);
    let rows = countries.map(c => ({ c, a: uVal(ia, ci, c, ma), b: uVal(ib, ci, c, mb) }))
      .filter(r => r.a != null && r.b != null);
    if (spec.order === 'alpha') rows.sort((x, y) => uTitle(x.c).localeCompare(uTitle(y.c)));
    else rows.sort((x, y) => y.a - x.a);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + spec.qa.toLowerCase() + '-lines.png'}>
        <ULines rows={rows} labels={spec.labels} />
      </UChartCard>);
  }

  if (spec.kind === 'radar') {
    const items = spec.items.map(it => {
      const item = UQ(data, it.q);
      return item ? { l: it.label, v: uGlobal(item, ci, ns, countries, uMatcher(spec.match)) } : null;
    }).filter(Boolean);
    if (!items.length) return null;
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + (spec.slug || 'radar') + '.png'}>
        <URadar items={items} max={spec.max} />
      </UChartCard>);
  }

  if (spec.kind === 'stackCols') {
    const item = UQ(data, spec.q);
    if (!item) return null;
    const groups = spec.groups;
    const sumRe = (c, re) => { let t = 0; item.answers.forEach(a => { if (new RegExp(re, 'i').test(a.label)) t += (a.values[ci[c]] || 0); }); return t; };
    const ov = spec.overlayQ ? UQ(data, spec.overlayQ) : null;
    const om = spec.overlayMatch ? uMatcher(spec.overlayMatch) : null;
    const sortIdx = spec.sortBy == null ? 0 : spec.sortBy;
    let rows = countries.map(c => {
      const raw = groups.map(g => sumRe(c, g.re));
      const tot = raw.reduce((x, y) => x + y, 0);
      if (!tot) return null;
      const r = { c, v: raw.map(v => v / tot) };
      if (ov) r.o = uVal(ov, ci, c, om);
      return r;
    }).filter(Boolean).sort((x, y) => y.v[sortIdx] - x.v[sortIdx]);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + spec.q.toLowerCase() + '-stackcols.png'}>
        <UStackCols rows={rows} groups={groups} overlayLabel={spec.overlayLabel} />
      </UChartCard>);
  }

  if (spec.kind === 'pies') {
    const panels = spec.panels.map(p => {
      const item = UQ(data, p.q);
      if (!item) return null;
      const items = p.groups.map(g => ({
        l: g.l, c: g.c,
        v: (() => { let t = 0; item.answers.forEach(a => { if (new RegExp(g.re, 'i').test(a.label)) t += (uGlobal(item, ci, ns, countries, l => l === a.label) || 0); }); return t; })(),
      }));
      return { title: p.title, items };
    }).filter(Boolean);
    if (!panels.length) return null;
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + (spec.slug || 'pies') + '.png'}>
        <UPies panels={panels} />
      </UChartCard>);
  }

  if (spec.kind === 'bandCols') {
    const item = UQ(data, spec.q);
    if (!item) return null;
    const bands = spec.bands || [{ re: '^[67]' }, { re: '^[345]' }, { re: '^[12]' }];
    const sumRe = (c, re) => { let t = 0; item.answers.forEach(a => { if (new RegExp(re, 'i').test(a.label)) t += (a.values[ci[c]] || 0); }); return t; };
    const ov = spec.overlayQ ? UQ(data, spec.overlayQ) : null;
    const om = spec.overlayMatch ? uMatcher(spec.overlayMatch) : null;
    let rows = countries.map(c => {
      const a = sumRe(c, bands[0].re), ne = sumRe(c, bands[1].re), d = sumRe(c, bands[2].re);
      const tot = a + ne + d;
      if (!tot) return null;
      const r = { c, agree: a / tot, neutral: ne / tot, disagree: d / tot };
      if (ov) r.o = uVal(ov, ci, c, om);
      return r;
    }).filter(Boolean).sort((x, y) => y.agree - x.agree);
    const Cmp = spec.horizontal ? UBandRows : UBandCols;
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + spec.q.toLowerCase() + '-cols.png'}>
        <Cmp rows={rows} legend={spec.legend} colors={spec.colors} overlayLabel={spec.overlayLabel} />
      </UChartCard>);
  }

  if (spec.kind === 'linesN') {
    const items = spec.series.map(s => ({ item: UQ(data, s.q), m: uMatcher(s.match), l: s.label }));
    if (items.some(s => !s.item)) return null;
    const rows = countries.map(c => ({ c, v: items.map(s => uVal(s.item, ci, c, s.m)) }))
      .filter(r => r.v.every(v => v != null)).sort((x, y) => y.v[spec.sortBy || 0] - x.v[spec.sortBy || 0]);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + (spec.slug || 'lines') + '.png'}>
        <ULinesN rows={rows} labels={items.map(s => s.l)} />
      </UChartCard>);
  }

  if (spec.kind === 'groupCols') {
    if (spec.series) {
      const items = spec.series.map(s => ({ item: UQ(data, s.q), m: uMatcher(s.match), l: s.label }));
      if (items.some(s => !s.item)) return null;
      const rows = countries.map(c => ({ l: uTitle(c), c, v: items.map(s => uVal(s.item, ci, c, s.m)) }))
        .filter(r => r.v.some(v => v != null)).sort((x, y) => (y.v[spec.sortBy || 0] || 0) - (x.v[spec.sortBy || 0] || 0));
      return (
        <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + (spec.slug || 'group') + '.png'}>
          <UGroupCols rows={rows} labels={items.map(s => s.l)} colors={spec.colors} height={spec.height} />
        </UChartCard>);
    }
    if (spec.answersOf) {
      const item = UQ(data, spec.answersOf);
      if (!item) return null;
      const rows = item.answers
        .filter(a => !(spec.exclude || []).some(x => a.label.toLowerCase().indexOf(x.toLowerCase()) >= 0))
        .map(a => ({ l: a.label.replace(/\s*\(.*$/, ''), v: [uGlobal(item, ci, ns, countries, l => l === a.label)] }))
        .sort((x, y) => (spec.asc ? x.v[0] - y.v[0] : y.v[0] - x.v[0]));
      return (
        <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + (spec.slug || 'group') + '.png'}>
          <UGroupCols rows={rows} labels={spec.labels} colors={spec.colors || ['var(--brand)']} height={spec.height} valueLabels />
        </UChartCard>);
    }
    const ia = UQ(data, spec.qa), ib = UQ(data, spec.qb);
    if (!ia || !ib) return null;
    let rows;
    if (spec.byAnswers) {
      // one column pair per answer option, values = global share in each question
      rows = ia.answers
        .filter(a => !(spec.exclude || []).some(x => a.label.toLowerCase().indexOf(x.toLowerCase()) >= 0))
        .map(a => {
          const key = a.label.replace(/\s*\(.*$/, '');
          const bMatch = ib.answers.find(x => x.label.replace(/\s*\(.*$/, '').toLowerCase() === key.toLowerCase());
          return { l: key, a: uGlobal(ia, ci, ns, countries, l => l === a.label), b: bMatch ? uGlobal(ib, ci, ns, countries, l => l === bMatch.label) : 0 };
        });
      // append options unique to the second question
      ib.answers.forEach(a => {
        const key = a.label.replace(/\s*\(.*$/, '');
        if (!rows.some(r => r.l.toLowerCase() === key.toLowerCase()) && !(spec.exclude || []).some(x => key.toLowerCase().indexOf(x.toLowerCase()) >= 0))
          rows.push({ l: key, a: 0, b: uGlobal(ib, ci, ns, countries, l => l === a.label) });
      });
      rows.sort((x, y) => (y.a + y.b) - (x.a + x.b));
    } else {
      const ma = uMatcher(spec.matchA), mb = uMatcher(spec.matchB);
      rows = countries.map(c => ({ l: uTitle(c), a: uVal(ia, ci, c, ma), b: uVal(ib, ci, c, mb) }))
        .filter(r => r.a != null && r.b != null).sort((x, y) => y.a - x.a);
    }
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + (spec.byAnswers ? '' : lowNote)} filename={'cbai-' + (spec.slug || 'group') + '.png'}>
        <UGroupCols rows={rows} labels={spec.labels} colors={spec.colors} height={spec.height} />
      </UChartCard>);
  }

  if (spec.kind === 'area') {
    const ia = UQ(data, spec.qa), ib = UQ(data, spec.qb);
    if (!ia || !ib) return null;
    const ma = uMatcher(spec.matchA), mb = uMatcher(spec.matchB);
    let rows = countries.map(c => ({ c, a: uVal(ia, ci, c, ma), b: uVal(ib, ci, c, mb) }))
      .filter(r => r.a != null && r.b != null);
    if (spec.bOfA) rows = rows.map(r => ({ ...r, b: r.b * r.a }));
    rows = rows.sort((x, y) => y.a - x.a);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + (spec.slug || 'area') + '.png'}>
        <UArea rows={rows} labels={spec.labels} />
      </UChartCard>);
  }

  if (spec.kind === 'medals') {
    const item = UQ(data, spec.q);
    const subs = spec.items ? spec.items.map(it => ({ item: UQ(data, it.q), l: it.label })) : null;
    const tally = {};
    countries.forEach(c => {
      let vals;
      if (subs) vals = subs.map(s => ({ l: s.l, v: s.item ? uVal(s.item, ci, c, uMatcher(spec.match)) : null }));
      else if (item) vals = item.answers.filter(a => !(spec.exclude || []).some(x => a.label.toLowerCase().indexOf(x.toLowerCase()) >= 0))
        .map(a => ({ l: a.label.replace(/\s*\(.*$/, ''), v: a.values[ci[c]] }));
      else return;
      vals = vals.filter(v => v.v != null).sort((x, y) => y.v - x.v).slice(0, 3);
      vals.forEach((v, rank) => {
        tally[v.l] = tally[v.l] || { l: v.l, g: 0, s: 0, b: 0 };
        tally[v.l][rank === 0 ? 'g' : rank === 1 ? 's' : 'b']++;
      });
    });
    const rows = Object.values(tally).sort((a, b) => (b.g * 100 + b.s * 10 + b.b) - (a.g * 100 + a.s * 10 + a.b));
    if (!rows.length) return null;
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + (spec.slug || 'medals') + '.png'}>
        <UMedals rows={rows} />
      </UChartCard>);
  }

  if (spec.kind === 'topReason') {
    const item = UQ(data, spec.q);
    if (!item) return null;
    const opts = item.answers.filter(a => !(spec.exclude || []).some(x => a.label.toLowerCase().indexOf(x.toLowerCase()) >= 0));
    const palette = ['var(--brand)', 'var(--cornell-navy)', 'var(--cornell-sea-gray)', '#F2A900', '#E8730C', 'var(--cornell-warm-gray)', 'var(--fg)'];
    const rows = countries.map(c => {
      let best = null;
      opts.forEach(a => { const v = a.values[ci[c]]; if (v != null && (!best || v > best.v)) best = { l: a.label.replace(/\s*\(.*$/, ''), v }; });
      return best ? { c, l: best.l, v: best.v } : null;
    }).filter(Boolean).sort((a, b) => b.v - a.v);
    const winners = [...new Set(rows.map(r => r.l))];
    const colorOf = {}; winners.forEach((w, i) => colorOf[w] = palette[i % palette.length]);
    rows.forEach(r => r.color = colorOf[r.l]);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + spec.q.toLowerCase() + '-top.png'}>
        <UTopReason rows={rows} legend={winners.map(w => [w, colorOf[w]])} />
      </UChartCard>);
  }

  if (spec.kind === 'treemap') {
    const item = UQ(data, spec.q);
    if (!item) return null;
    const extra = spec.plus ? UQ(data, spec.plus) : null;
    let items = item.answers.filter(a => !(spec.exclude || []).some(x => a.label.toLowerCase().indexOf(x.toLowerCase()) >= 0))
      .map(a => ({ l: a.label.replace(/\s*\(.*$/, ''), v: uGlobal(item, ci, ns, countries, l => l === a.label) }));
    if (extra) extra.answers.forEach(a => {
      const key = a.label.replace(/\s*\(.*$/, '');
      const hit = items.find(r => r.l.toLowerCase() === key.toLowerCase());
      const v = uGlobal(extra, ci, ns, countries, l => l === a.label);
      if (hit) hit.v = (hit.v + v) / 2; else items.push({ l: key, v });
    });
    items = items.filter(i => i.v > 0).sort((a, b) => b.v - a.v);
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot} filename={'cbai-' + spec.q.toLowerCase() + '-treemap.png'}>
        <UTreemap items={items} />
      </UChartCard>);
  }

  if (spec.kind === 'stack') {
    const item = UQ(data, spec.q);
    if (!item) return null;
    const order = spec.orderBy
      ? countries.slice().sort((a, b) => (uVal(item, ci, b, uMatcher(spec.orderBy)) || 0) - (uVal(item, ci, a, uMatcher(spec.orderBy)) || 0))
      : countries;
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + spec.q.toLowerCase() + '-stack.png'}>
        <UStack item={item} ci={ci} countries={order} />
      </UChartCard>);
  }

  if (spec.kind === 'compare') {
    const ia = UQ(data, spec.qa), ib = UQ(data, spec.qb);
    if (!ia || !ib) return null;
    const useMean = spec.mode === 'mean';
    const ma = uMatcher(spec.matchA), mb = uMatcher(spec.matchB);
    let rows = countries.map(c => ({
      c,
      a: useMean ? uMean(ia, ci, c) : uVal(ia, ci, c, ma),
      b: useMean ? uMean(ib, ci, c) : uVal(ib, ci, c, mb),
    })).filter(r => r.a != null && r.b != null);
    rows.sort((x, y) => (y.a - y.b) - (x.a - x.b));
    return (
      <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-compare-' + spec.qa.toLowerCase() + '.png'}>
        <UCompare rows={rows} labels={spec.labels} max={spec.max} fmt={useMean ? (v => v.toFixed(2)) : null} />
      </UChartCard>);
  }

  // rank / meanRank
  const { rows, glob, missing } = uSpecRows(spec, data);
  if (missing) return null;
  return (
    <UChartCard title={spec.title} sub={spec.sub} footnote={foot + lowNote} filename={'cbai-' + spec.q.toLowerCase() + '-rank.png'}>
      <URankBar rows={rows} fmt={fmtMean} max={spec.max} accent={spec.accent}
        note={glob != null && spec.showGlobal !== false ? ('25-country average: ' + uFmt(glob, 1)) : null} />
    </UChartCard>);
}

function UProse({ html }) {
  return <div className="upost-prose" dangerouslySetInnerHTML={{ __html: html }} />;
}

function UCallout({ title, items, body }) {
  return (
    <div style={{ background: 'var(--surface-2)', border: '1px solid var(--rule)', borderLeft: '3px solid var(--brand)', padding: '18px 22px', margin: '28px 0' }}>
      {title && <div className="eyebrow sans" style={{ marginBottom: 10 }}>{title}</div>}
      {body && <p style={{ margin: 0, fontSize: 16.5 }}>{body}</p>}
      {items && <ul style={{ margin: 0, paddingLeft: 20, display: 'grid', gap: 7 }}>
        {items.map((t, i) => <li key={i} style={{ fontSize: 16 }} dangerouslySetInnerHTML={{ __html: t }} />)}
      </ul>}
    </div>);
}

function UPostPage({ data, slug, setRoute }) {
  const [post, setPost] = useState(null);
  const [err, setErr] = useState(null);
  useEffect(() => {
    setPost(null); setErr(null);
    const inline = window.__INLINE_POSTS && window.__INLINE_POSTS[slug];
    if (inline) { setPost(inline); return; }
    fetch('data/posts/' + slug + '.json').then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(setPost).catch(e => setErr(String(e)));
  }, [slug]);

  if (err) return (
    <section className="section"><div className="container"><p className="sans">That post could not be loaded. <button className="sans" onClick={() => setRoute('updates')} style={{ background: 'none', border: 0, textDecoration: 'underline', padding: 0 }}>Back to Updates</button></p></div></section>);
  if (!post) return <section className="section"><div className="container"><div className="eyebrow sans">Loading…</div></div></section>;

  const all = (window.__POST_INDEX || []);
  const i = all.findIndex(p => p.slug === slug);
  const prev = i > 0 ? all[i - 1] : null, next = i >= 0 && i < all.length - 1 ? all[i + 1] : null;

  return (
    <article className="section" style={{ paddingTop: 40 }}>
      <div className="container" style={{ maxWidth: 900 }}>
        <button className="sans" onClick={() => setRoute('updates')}
          style={{ background: 'none', border: 0, padding: 0, color: 'var(--muted)', fontFamily: 'Inter Tight, sans-serif', fontSize: 12.5, marginBottom: 26 }}>
          ← All Updates
        </button>
        <div className="eyebrow sans" style={{ marginBottom: 14 }}>{post.eyebrow || ('Week ' + post.week)}</div>
        <h1 style={{ fontSize: 'clamp(30px, 4.2vw, 50px)', maxWidth: '26ch' }}>{post.title}</h1>
        {post.dek && <p className="lede" style={{ fontSize: 20, color: 'var(--ink-2)', maxWidth: '58ch', marginTop: 20 }}>{post.dek}</p>}
        <div className="src" style={{ marginTop: 22, paddingTop: 14, borderTop: '1px solid var(--rule)', display: 'flex', flexWrap: 'wrap', gap: '4px 18px' }}>
          <span>Published {post.date}</span>
          <span>Data updated {post.updated}</span>
          <span>Cornell Bitcoin Adoption Index</span>
        </div>

        <div style={{ marginTop: 34 }}>
          {post.sections.map((s, k) => {
            if (s.type === 'h') return <h2 key={k} style={{ fontSize: 27, marginTop: 46, marginBottom: 16, maxWidth: '30ch' }}>{s.text}</h2>;
            if (s.type === 'p') return <UProse key={k} html={s.html} />;
            if (s.type === 'callout') return <UCallout key={k} {...s} />;
            if (s.type === 'chart') return <UChart key={k} spec={s} data={data} />;
            if (s.type === 'note') return (
              <div key={k} className="src" style={{ margin: '22px 0', padding: '12px 16px', border: '1px dashed var(--rule)', lineHeight: 1.55 }}>{s.text}</div>);
            return null;
          })}
        </div>

        <nav className="sans" style={{ display: 'flex', justifyContent: 'space-between', gap: 20, marginTop: 60, paddingTop: 22, borderTop: '1px solid var(--rule)', fontFamily: 'Inter Tight, sans-serif', fontSize: 13.5 }}>
          <span>{prev && <a href={window.routeHref(prev.slug)} onClick={(e)=>{e.preventDefault(); window.navTo(prev.slug);}} style={{ color: 'var(--ink-2)', textDecoration: 'none' }}>← {prev.title}</a>}</span>
          <span>{next && <a href={window.routeHref(next.slug)} onClick={(e)=>{e.preventDefault(); window.navTo(next.slug);}} style={{ color: 'var(--ink-2)', textDecoration: 'none' }}>{next.title} →</a>}</span>
        </nav>
      </div>
    </article>);
}

Object.assign(window, { UChart, UPostPage, UProse, UCallout });
