// Autoresearch.jsx — the blog index and the single-article view.
//
// Posts live as plain markdown in /content/autoresearch/<slug>.md, listed in
// /content/autoresearch/posts.json. Nothing here talks to the private
// autoresearch repository: a post exists on the site only once a human has run
// scripts/publish-autoresearch.py for it, which is the release gate.
//
// Internal grading (grade.json: letter, score, triage verdict, evidence gates)
// is deliberately NOT published. A reader has no way to calibrate a "B" and
// reads it as a demerit.

const POSTS_INDEX = '/content/autoresearch/posts.json';
const POSTS_DIR = '/content/autoresearch/';

/* Shared page furniture, so the index and an article share one header. */
function AutoresearchPage({ children }) {
  return (
    <div data-screen-label="hackhack.ai · autoresearch">
      <Nav />
      {children}
      <Footer />
    </div>
  );
}

/* ── Index ──────────────────────────────────────────────────────────────── */

function AutoresearchIndex() {
  const [posts, setPosts] = React.useState(null);
  const [failed, setFailed] = React.useState(false);

  React.useEffect(() => {
    fetch(POSTS_INDEX)
      .then((r) => { if (!r.ok) throw new Error(r.status); return r.json(); })
      .then((list) => setPosts(sortByDateDesc(list)))
      .catch(() => setFailed(true));
  }, []);

  return (
    <AutoresearchPage>
      <section className="section section-rule" style={{ paddingTop: 'clamp(56px, 7vw, 96px)' }}>
        <div className="container">
          <div className="section-intro" style={{ maxWidth: 720 }}>
            <span className="eyebrow">autoresearch</span>
            <h1 className="h-section">
              Original Solana security research.<br />
              <span className="h-quiet">Fully created using hackhack.</span>
            </h1>
            <p className="lead">
              New vulnerabilities, undocumented runtime behaviour, bug classes and bypasses
              across Solana and the SVM. Every article here was researched, tested and written
              by hackhack itself. We publish it because the ecosystem is safer when this work
              is in the open.
            </p>
          </div>

          {failed && (
            <div className="ar-empty">
              The article index could not be loaded. Try a refresh.
            </div>
          )}

          {!failed && posts === null && (
            <div className="ar-empty">Loading…</div>
          )}

          {!failed && posts !== null && posts.length === 0 && (
            <div className="ar-empty">
              Nothing published yet. Research that clears review lands here first.
            </div>
          )}

          {!failed && posts !== null && posts.length > 0 && (
            <div className="ar-grid">
              {posts.map((p) => (
                <a className="ar-card" key={p.slug} href={`/autoresearch/${p.slug}`}>
                  {p.image && (
                    <div className="ar-card-img">
                      {/* The share card doubles as the index thumbnail: one
                          generated artefact, so the two can never disagree. */}
                      <img src={p.image} alt="" loading="lazy" width="1200" height="630" />
                    </div>
                  )}
                  <div className="ar-card-body">
                    <div className="ar-meta">
                      <time dateTime={p.date}>{formatDate(p.date)}</time>
                      {p.topic && <span>{p.topic}</span>}
                    </div>
                    <h2 className="ar-title">{p.title}</h2>
                    {p.summary && <p className="ar-summary">{p.summary}</p>}
                  </div>
                </a>
              ))}
            </div>
          )}
        </div>
      </section>
    </AutoresearchPage>
  );
}

/* ── Article ────────────────────────────────────────────────────────────── */

// The slug and title come from the generated page shell (see the publish
// script) so the document has a real <title> and OG tags before any JS runs.
function AutoresearchPost({ slug, title, date, topic }) {
  const [html, setHtml] = React.useState(null);
  const [failed, setFailed] = React.useState(false);

  React.useEffect(() => {
    fetch(`${POSTS_DIR}${slug}.md`)
      .then((r) => { if (!r.ok) throw new Error(r.status); return r.text(); })
      .then((md) => setHtml(renderMarkdown(md)))
      .catch(() => setFailed(true));
  }, [slug]);

  return (
    <AutoresearchPage>
      <section className="section section-rule" style={{ paddingTop: 'clamp(48px, 6vw, 80px)' }}>
        <div className="container">
          <header className="post-head">
            <a className="post-back" href="/autoresearch/">← Autoresearch</a>
            <h1 className="post-title">{title}</h1>
            <div className="post-meta">
              <div className="ar-meta" style={{ margin: 0 }}>
                <AuthorTag />
                <time dateTime={date}>{formatDate(date)}</time>
                {topic && <span>{topic}</span>}
              </div>
              <ShareLinks title={title} />
            </div>
          </header>

          {failed && <div className="ar-empty">This article could not be loaded. Try a refresh.</div>}
          {!failed && html === null && <div className="ar-empty">Loading…</div>}
          {!failed && html !== null && (
            <React.Fragment>
              <article className="prose" dangerouslySetInnerHTML={{ __html: html }} />
              <div className="post-foot">
                <span className="ar-meta" style={{ margin: 0 }}>Share this research</span>
                <ShareLinks title={title} />
              </div>
            </React.Fragment>
          )}
        </div>
      </section>
    </AutoresearchPage>
  );
}

function AuthorTag() {
  return (
    <span className="post-author" aria-label="Author: hackhack">
      <img src="/assets/hackhack/logo.svg" alt="" width="28" height="22" />
      <span>by hackhack</span>
    </span>
  );
}

/* ── Sharing ────────────────────────────────────────────────────────────── */

// Plain intent links, no third-party widgets: a share button that phones home
// to X or LinkedIn on page load would track every reader who never clicks it.
function ShareLinks({ title }) {
  const [copied, setCopied] = React.useState(false);
  // Read at click time rather than render time so it is correct under SSR-less
  // hydration and after any client-side navigation.
  const url = () => window.location.href;

  const copy = (event) => {
    event.preventDefault();
    navigator.clipboard.writeText(url()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    }, () => {});
  };

  return (
    <div className="share">
      <a
        className="share-btn"
        href={`https://x.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(typeof window === 'undefined' ? '' : window.location.href)}`}
        target="_blank" rel="noopener noreferrer"
        aria-label="Share on X"
      >
        <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
          <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
        </svg>
        Post
      </a>
      <a
        className="share-btn"
        href={`https://news.ycombinator.com/submitlink?u=${encodeURIComponent(typeof window === 'undefined' ? '' : window.location.href)}&t=${encodeURIComponent(title)}`}
        target="_blank" rel="noopener noreferrer"
        aria-label="Submit to Hacker News"
      >
        HN
      </a>
      <button className="share-btn" type="button" onClick={copy} aria-live="polite">
        {copied ? 'Copied' : 'Copy link'}
      </button>
    </div>
  );
}

/* ── Helpers ────────────────────────────────────────────────────────────── */

// Markdown comes from our own repository and is human-cleared before it gets
// here, so it is trusted input; marked does not sanitise. If this ever renders
// text from outside that path, put DOMPurify in front of it.
function renderMarkdown(md) {
  // The article's own H1 is dropped: the page header already shows the title,
  // and two of them would be both ugly and wrong for a screen reader.
  const body = md.replace(/^\s*#\s+.*(\r?\n)+/, '');
  const html = window.marked.parse(body, { gfm: true, breaks: false });
  // Tables get a scroll container so a wide one cannot push the page sideways.
  return html.replace(/<table>/g, '<div class="table-wrap"><table>')
             .replace(/<\/table>/g, '</table></div>');
}

function sortByDateDesc(list) {
  return [...list].sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));
}

function formatDate(iso) {
  if (!iso) return '';
  // Parsed as UTC on purpose: `new Date('2026-08-31')` is midnight UTC, and
  // formatting it in a negative-offset timezone would show the previous day.
  const [y, m, d] = iso.split('-').map(Number);
  const date = new Date(Date.UTC(y, m - 1, d));
  return date.toLocaleDateString('en-GB', {
    day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC',
  });
}

window.AutoresearchIndex = AutoresearchIndex;
window.AutoresearchPost = AutoresearchPost;
