function Certificate({ projectId, onNavigate }) {
  const { t, lang, setLang } = useI18n();
  // Hook — must stay above the early returns below.
  const chainNotice = useChainNotice();
  const [data, setData] = React.useState(null);
  const [notFound, setNotFound] = React.useState(false);
  const [showEmbed, setShowEmbed] = React.useState(false);

  React.useEffect(() => {
    fetch(`/api/certificate/${projectId}`)
      .then((r) => {
        if (!r.ok) { setNotFound(true); return null; }
        return r.json();
      })
      .then((body) => { if (body) setData(body); })
      .catch(() => setNotFound(true));
  }, [projectId]);

  if (notFound) {
    return <div className="container py-48"><div className="h3">{t('detail.notFound')}</div></div>;
  }
  if (!data) return <div className="container py-48" />;

  const { project, owner } = data;
  const scan = project.chain === 'polygon' ? 'PolygonScan' : 'Etherscan';
  const chainName = project.chain === 'polygon' ? 'Polygon' : 'Ethereum';
  const dateLocale = lang === 'ko' ? 'ko-KR' : 'en-US';
  const authorShort = owner.slice(0, 6) + '…' + owner.slice(-4);
  const sealedDate = project.sealedAt
    ? new Date(project.sealedAt).toLocaleDateString(dateLocale, { month: 'long', day: 'numeric', year: 'numeric' })
    : '';

  // The short public URL the server redirects to this page (see the `/c/:id`
  // route in server/src/index.js) — this is what every share action hands out.
  const shareUrl = `${window.location.origin}/c/${project.id}`;
  const embedCode =
    `<iframe src="${shareUrl}" width="100%" height="1200" loading="lazy" ` +
    `style="border:1px solid #e6dfd8;border-radius:12px" ` +
    `title="JervisBox — ${escapeXml(project.title)}"></iframe>`;

  return (
    <div className="cert-page" style={{ background: 'var(--surface-soft)', minHeight: 'calc(100vh)' }}>
      {/* Public bar */}
      <div className="pub-bar">
        <div className="container between">
          <div className="row gap-12">
            {/* This page hides the global Nav, so the brand mark is the only
                way back into the app — for the owner and for a stranger who
                arrived from a shared link. */}
            <a
              className="pub-brand"
              href="#landing"
              title={t('cert.backToHome')}
              onClick={(e) => { e.preventDefault(); onNavigate('landing'); }}
            >
              <BrandGlyph className="anthropic-mark pub-brand-glyph" />
              <span className="pub-brand-mark">Jervis<em>Box</em></span>
            </a>
            <span className="tiny" style={{ color:'var(--on-dark)' }}>{t('cert.publicBar')}</span>
            <span className="mono" style={{ fontSize: 11, color:'var(--on-dark-soft)' }}>{shareUrl}</span>
          </div>
          <div className="row gap-8">
            <button
              className="btn btn-sm btn-secondary"
              onClick={() => setLang(lang === 'en' ? 'ko' : 'en')}
              aria-label={t('lang.label')}
              style={{ fontFamily:'var(--mono)', letterSpacing:'0.08em' }}
            >
              {lang === 'en' ? 'EN · 한국어' : 'KO · English'}
            </button>
            <CopyButton className="btn btn-sm btn-secondary" text={shareUrl} label={t('common.copyURL')} />
            <button
              className="btn btn-sm"
              style={{ background:'var(--canvas)', color:'var(--ink)' }}
              onClick={() => window.print()}
            >
              {t('common.downloadPDF')}
            </button>
          </div>
        </div>
      </div>

      <div className="container-narrow" style={{ padding:'60px 32px 120px' }}>
        <div className="certificate">
          {/* Header */}
          <div className="row between mb-48" style={{ alignItems:'flex-start' }}>
            <div>
              <div className="tiny">{t('cert.title')}</div>
              <div className="mono mt-4" style={{ fontSize: 11, color:'var(--muted)' }}>{project.id} · {t('cert.issuedBy')}</div>
            </div>
            <div className="certificate-seal">
              <div style={{ fontFamily:'var(--serif)', fontSize: 12 }}>{t('cert.sealed')}</div>
              <div className="mono" style={{ fontSize: 9, marginTop: 4, letterSpacing:'0.12em' }}>
                {project.sealedAt ? new Date(project.sealedAt).toISOString().slice(5,10).replace('-','·') : ''}
              </div>
            </div>
          </div>

          {/* Title */}
          <div className="mb-48">
            <div className="serif cert-display-title" style={{ lineHeight: 1, letterSpacing:'-0.028em' }}>
              {project.title}
            </div>
            <div className="serif italic mt-16 ink-3" style={{ fontSize: 22 }}>
              {project.subtitle}
            </div>
          </div>

          {/* Author + date */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 32, marginBottom: 40, paddingBottom: 32, borderBottom:'1px solid var(--hairline)' }}>
            <div className="col gap-4">
              <div className="tiny">{t('cert.authoredBy')}</div>
              <div className="mono" style={{ fontSize: 18 }}>{authorShort}</div>
            </div>
            <div className="col gap-4">
              <div className="tiny">{t('cert.sealedOn')}</div>
              <div className="serif" style={{ fontSize: 24 }}>{sealedDate}</div>
              <div className="mono small ink-3">{project.sealedAt ? fmtTimeUTC(project.sealedAt) : ''}</div>
            </div>
          </div>

          {/* Hash */}
          <div className="mb-40">
            <div className="tiny mb-16">{t('cert.finalMerkle')}</div>
            <div className="hash-mono-lg cert-final-hash">{project.finalHash}</div>
          </div>

          {/* Stats */}
          <div style={{ display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap: 24, marginBottom: 40, paddingBottom: 32, borderBottom:'1px solid var(--hairline)' }}>
            <CertStat label={t('cert.stat.events')} value={project.stats.events} />
            <CertStat label={t('cert.stat.aiOps')} value={project.stats.aiOps} />
            <CertStat label={t('cert.stat.humanEdits')} value={project.stats.humanEdits} />
            <CertStat label={t('cert.stat.duration')} value={project.stats.duration} small />
          </div>

          {/* Process summary */}
          <div className="mb-40">
            <div className="tiny mb-24">{t('cert.processSummary')}</div>
            <div style={{ display:'grid', gridTemplateColumns:'auto 1fr auto', columnGap: 24, rowGap: 14, alignItems:'baseline' }}>
              {project.events.map((e, i) => (
                <React.Fragment key={e.id}>
                  <div className="mono" style={{ fontSize: 11, color:'var(--muted)' }}>{String(i+1).padStart(2, '0')}</div>
                  <div>
                    <span style={{ fontSize: 14 }}>{e.label}</span>
                    {e.model && <span className="ink-3 small"> · {e.model}</span>}
                  </div>
                  <div className="row gap-8">
                    {e.who === 'ai' && <span className="badge badge-ai">{t('common.actorAI')}</span>}
                    {e.who === 'human' && <span className="badge badge-human">{t('common.actorHuman')}</span>}
                    {e.who === 'chain' && <span className="badge">{t('common.actorChain')}</span>}
                    <span className="mono" style={{ fontSize:11, color:'var(--muted)' }}><ShortHash hash={e.hash} len={4} /></span>
                  </div>
                </React.Fragment>
              ))}
            </div>
          </div>

          {/* Chain */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 160px', gap: 40, alignItems:'center', marginBottom: 40, paddingBottom: 32, borderBottom:'1px solid var(--hairline)' }}>
            <div>
              <div className="tiny mb-8">{t('cert.onChainRecord')}</div>
              <div className="row gap-12 mb-8">
                <ChainBadge chain={project.chain} />
                <span className="badge badge-verified"><span className="dot-verified" /> {t('common.confirmed')}</span>
              </div>
              {/* Sits right under the "Confirmed" badge — the qualifier belongs
                  next to the claim it qualifies, and unlike the public bar this
                  is inside the document, so it prints and embeds too. */}
              <ChainNoticeInline variant="cert" />
              <div className="col gap-4 mt-16">
                <div className="tiny">{t('cert.transaction')}</div>
                <div className="mono small" style={{ wordBreak:'break-all' }}>{project.txHash}</div>
              </div>
              <a
                className="btn btn-secondary btn-sm mt-16"
                style={{ alignSelf:'flex-start', display:'inline-block' }}
                href={'#tx:' + project.txHash}
                onClick={(e) => { e.preventDefault(); onNavigate('tx:' + project.txHash); }}
              >
                {t('common.viewExplorer')}
              </a>
            </div>
            <TxQrCode txHash={project.txHash} onNavigate={onNavigate} />
          </div>

          {/* How to verify */}
          <div className="mt-32">
            <div className="tiny mb-16">{t('cert.howToVerify')}</div>
            <div className="col gap-8" style={{ fontSize: 14, lineHeight: 1.7, color:'var(--body)' }}>
              <div>{t('cert.verify.1')}</div>
              <div>{t('cert.verify.2', { scan })}</div>
              <div>{t('cert.verify.3')}</div>
              <div>{t('cert.verify.4')}</div>
            </div>
          </div>

          {/* Sign-off */}
          <div className="mt-48 row between" style={{ paddingTop: 32, borderTop:'1px dashed var(--hairline)' }}>
            <div>
              <div className="serif italic" style={{ fontSize: 32, letterSpacing:'-0.01em' }}>{authorShort}</div>
              <div className="tiny mt-4">{t('cert.signature')} · {authorShort}</div>
            </div>
            <div className="text-right">
              <div className="tiny">{t('cert.issuedBy').replace('issued by ', '').replace(' 발행', '')}</div>
              <div className="serif mt-4" style={{ fontSize: 20 }}>Jervis<em className="italic" style={{ color:'var(--primary)' }}>Box</em></div>
              <div className="tiny mt-4" style={{ textTransform:'none', letterSpacing:0 }}>jervisbox.io</div>
            </div>
          </div>
        </div>

        {/* Share strip */}
        <div className="mt-32 card-flat cert-share-strip" style={{ padding: 20 }}>
          <div className="between">
            <div>
              <div className="tiny mb-4">{t('cert.share')}</div>
              <div className="mono small" style={{ wordBreak:'break-all' }}>{shareUrl}</div>
            </div>
            <div className="row gap-8">
              <CopyButton text={shareUrl} label={t('common.copyLink')} />
              <button className="btn btn-secondary btn-sm" onClick={() => setShowEmbed((v) => !v)}>
                {t('common.embed')}
              </button>
              <button className="btn btn-secondary btn-sm" onClick={() => window.print()}>
                {t('common.downloadPDF')}
              </button>
              <button
                className="btn btn-secondary btn-sm"
                onClick={() => downloadSvgCard({ project, authorShort, sealedDate, chainName, notice: chainNotice && chainNotice.tag })}
              >
                {t('common.svgCard')}
              </button>
            </div>
          </div>

          {/* No modal primitive exists in this app — an inline panel keeps the
              public page dependency-free and works fine inside an iframe too. */}
          {showEmbed && (
            <div className="cert-embed-panel mt-16">
              <div className="between mb-8">
                <div className="tiny">{t('cert.embedTitle')}</div>
                <button className="btn btn-secondary btn-sm" onClick={() => setShowEmbed(false)}>
                  {t('common.close')}
                </button>
              </div>
              <div className="small ink-3 mb-8">{t('cert.embedHint')}</div>
              <textarea className="cert-embed-code" readOnly rows={3} value={embedCode} onFocus={(e) => e.target.select()} />
              <div className="row gap-8 mt-8">
                <CopyButton text={embedCode} label={t('common.copyCode')} />
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Real, scannable QR (qrcode-generator, loaded via CDN in index.html) —
// encodes the same in-app explorer URL the "View in explorer" button
// navigates to, so scanning it with a phone opens the identical transaction
// detail page in the browser.
function TxQrCode({ txHash, onNavigate }) {
  const svg = React.useMemo(() => {
    const url = `${window.location.origin}/#tx:${txHash}`;
    const qr = window.qrcode(0, 'M');
    qr.addData(url);
    qr.make();
    return qr.createSvgTag({ cellSize: 4, margin: 4, scalable: true }).replace('<svg ', '<svg width="100%" height="100%" ');
  }, [txHash]);

  return (
    <div
      style={{ width: 140, height: 140, border: '1px solid var(--hairline)', padding: 8, cursor: 'pointer' }}
      onClick={() => onNavigate('tx:' + txHash)}
      title="Scan or click to view this transaction"
      dangerouslySetInnerHTML={{ __html: svg }}
    />
  );
}

// Project titles/subtitles are user-supplied text going straight into an XML
// document — escape before interpolating.
function escapeXml(s) {
  return String(s == null ? '' : s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

function truncate(s, max) {
  const str = String(s || '');
  return str.length > max ? str.slice(0, max - 1) + '…' : str;
}

// A standalone 1200×630 (OG image ratio) share card. Everything is inlined:
// CSS custom properties and color-mix() don't resolve in a downloaded SVG, and
// the Google Fonts the app uses aren't embedded either — hence literal hex
// colours and generic font stacks rather than var(--canvas)/var(--serif).
function buildCertificateSvg({ project, authorShort, sealedDate, chainName, notice }) {
  const CANVAS = '#faf9f5', INK = '#141413', PRIMARY = '#cc785c', HAIRLINE = '#e6dfd8', MUTED = '#6c6a64';
  // A downloaded SVG can't carry the app's webfonts, so these are generic
  // stacks. Pretendard is named first anyway — it's widely installed locally
  // in Korea, and when it is present the Hangul matches the on-screen page.
  const SERIF = "Georgia, 'Times New Roman', 'Pretendard Variable', Pretendard, serif";
  const MONO = "ui-monospace, 'SFMono-Regular', Menlo, Consolas, 'Pretendard Variable', monospace";

  const root = project.finalHash || '';
  const rootHead = root.slice(0, 34);
  const rootTail = root.slice(34);
  // Same MM·DD stamp the on-page seal shows — the project id is far too long
  // to fit inside the circle.
  const sealStamp = project.sealedAt
    ? new Date(project.sealedAt).toISOString().slice(5, 10).replace('-', '·')
    : '';

  return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-label="JervisBox certificate for ${escapeXml(project.title)}">
  <rect width="1200" height="630" fill="${CANVAS}"/>
  <rect x="24" y="24" width="1152" height="582" fill="none" stroke="${HAIRLINE}" stroke-width="1"/>
  <path d="M56 104 V56 H104" fill="none" stroke="${INK}" stroke-width="1.5"/>
  <path d="M1144 526 V574 H1096" fill="none" stroke="${INK}" stroke-width="1.5"/>

  <text x="72" y="106" font-family="${SERIF}" font-size="13" letter-spacing="3.2" fill="${MUTED}">CERTIFICATE OF CREATIVE PROVENANCE</text>
  <text x="1128" y="98" font-family="${SERIF}" font-size="22" fill="${INK}" text-anchor="end">JervisBox</text>
  <text x="1128" y="118" font-family="${SERIF}" font-size="11" fill="${MUTED}" text-anchor="end">jervisbox.io</text>

  <text x="72" y="196" font-family="${SERIF}" font-size="58" fill="${INK}">${escapeXml(truncate(project.title, 34))}</text>
  <text x="72" y="238" font-family="${SERIF}" font-size="22" font-style="italic" fill="${MUTED}">${escapeXml(truncate(project.subtitle, 68))}</text>

  <line x1="72" y1="278" x2="1128" y2="278" stroke="${HAIRLINE}" stroke-width="1"/>

  <text x="72" y="312" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">AUTHORED BY</text>
  <text x="72" y="344" font-family="${MONO}" font-size="20" fill="${INK}">${escapeXml(authorShort)}</text>
  <text x="452" y="312" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">SEALED ON</text>
  <text x="452" y="344" font-family="${SERIF}" font-size="20" fill="${INK}">${escapeXml(sealedDate)}</text>
  <text x="832" y="312" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">CHAIN</text>
  <text x="832" y="344" font-family="${SERIF}" font-size="20" fill="${INK}">${escapeXml(chainName)}</text>

  <text x="72" y="404" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">FINAL MERKLE ROOT</text>
  <text x="72" y="434" font-family="${MONO}" font-size="19" fill="${INK}">${escapeXml(rootHead)}</text>
  <text x="72" y="460" font-family="${MONO}" font-size="19" fill="${INK}">${escapeXml(rootTail)}</text>

  <line x1="72" y1="500" x2="1128" y2="500" stroke="${HAIRLINE}" stroke-width="1"/>

  <text x="72" y="536" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">EVENTS</text>
  <text x="72" y="566" font-family="${SERIF}" font-size="26" fill="${INK}">${project.stats.events}</text>
  <text x="212" y="536" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">AI OPS</text>
  <text x="212" y="566" font-family="${SERIF}" font-size="26" fill="${INK}">${project.stats.aiOps}</text>
  <text x="352" y="536" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">HUMAN EDITS</text>
  <text x="352" y="566" font-family="${SERIF}" font-size="26" fill="${INK}">${project.stats.humanEdits}</text>
  <text x="532" y="536" font-family="${SERIF}" font-size="11" letter-spacing="2.4" fill="${MUTED}">DURATION</text>
  <text x="532" y="566" font-family="${SERIF}" font-size="26" fill="${INK}">${escapeXml(project.stats.duration)}</text>

  <circle cx="1064" cy="404" r="52" fill="${PRIMARY}"/>
  <circle cx="1064" cy="404" r="60" fill="none" stroke="${PRIMARY}" stroke-width="1" stroke-dasharray="4 4" opacity="0.5"/>
  <text x="1064" y="402" font-family="${SERIF}" font-size="14" letter-spacing="1.5" fill="#ffffff" text-anchor="middle">SEALED</text>
  <text x="1064" y="422" font-family="${MONO}" font-size="11" letter-spacing="1.2" fill="#ffffff" text-anchor="middle">${escapeXml(sealStamp)}</text>
${notice ? `
  <text x="72" y="140" font-family="${SERIF}" font-size="12" letter-spacing="1.6" fill="${PRIMARY}">${escapeXml(notice)}</text>` : ''}
</svg>`;
}

function downloadSvgCard(args) {
  const blob = new Blob([buildCertificateSvg(args)], { type: 'image/svg+xml;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `jervisbox-certificate-${args.project.id}.svg`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  // Revoking synchronously can cancel the download that click() just kicked
  // off — let the browser pick the blob up first.
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

function CertStat({ label, value, small }) {
  return (
    <div className="col gap-4">
      <div className="tiny">{label}</div>
      <div className="serif" style={{ fontSize: small ? 20 : 28, lineHeight: 1, letterSpacing:'-0.01em' }}>{value}</div>
    </div>
  );
}

window.Certificate = Certificate;
