// ─────────────────────────────────────────────────────────────────────────
// components.jsx — page-section components for the blockscanner site.
//
// All sections read their copy from window.{SITE,NAV,HERO,STATS,PRODUCTS,CONTACT,RESEARCH}
// (see content.jsx). Page templates (landing.html, research.html) just
// compose these and don't carry copy directly.
// ─────────────────────────────────────────────────────────────────────────

async function submitForm(endpoint, body, opts = {}) {
  const url = `${window.__bs_api || ""}${endpoint}`;
  const headers = { "x-csrf-token": window.__bs_csrf };
  if (!opts.multipart) headers["content-type"] = "application/json";
  const fingerprint = await window.__bs_fingerprint;
  const enriched = {
    ...body,
    _hp: body._hp ?? "",
    form_token: window.__bs_form_token,
    fingerprint,
    hcaptcha_token: window.hcaptcha?.getResponse?.() ?? "",
  };
  const payload = opts.multipart ? body : JSON.stringify(enriched);
  const res = await fetch(url, {
    method: "POST",
    credentials: "include",
    headers,
    body: payload,
  });
  if (!res.ok) {
    let errMsg = `${res.status}`;
    try {
      const j = await res.json();
      if (j.error) errMsg = j.error;
    } catch {}
    throw new Error(errMsg);
  }
}

// Adds a `is-revealed` class to every [data-reveal] element as it scrolls
// into view. Used for subtle entrance animations across the page.
function useReveal() {
  React.useEffect(() => {
    const els = document.querySelectorAll("[data-reveal]");
    if (!("IntersectionObserver" in window) || matchMedia("(prefers-reduced-motion: reduce)").matches) {
      els.forEach((el) => el.classList.add("is-revealed"));
      return;
    }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("is-revealed");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.04, rootMargin: "0px 0px -8% 0px" }
    );
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  }, []);
}

// Smoothly scrolls anchor links inside the page so jumping between sections
// isn't a hard cut.
function useAnchorScroll() {
  React.useEffect(() => {
    const onClick = (e) => {
      const a = e.target.closest("a[href^='#']");
      if (!a) return;
      const href = a.getAttribute("href");
      if (!href || href === "#") return;
      const el = document.querySelector(href);
      if (!el) return;
      e.preventDefault();
      el.scrollIntoView({ behavior: "smooth", block: "start" });
    };
    document.addEventListener("click", onClick);
    return () => document.removeEventListener("click", onClick);
  }, []);
}

// ───────────────────────────────────────────────────────── Header / footer

function BSHeader({ theme, onToggle }) {
  const features = window.FEATURES || {};
  return (
    <header className="bs-nav">
      <a href="/" className="bs-wordmark">{SITE.name}</a>
      <nav className="bs-navlinks">
        {NAV.map((n) => {
          if (n.label === "Products" && features.dropdownNav) {
            return <BSNavProductsDropdown key={n.href} item={n} />;
          }
          return (
            <a key={n.href} href={n.href} className="bs-navlink">
              <span>{n.label}</span>
            </a>
          );
        })}
      </nav>
      <div className="bs-navright">
        {features.auth ? (
          <div className="bs-nav-auth">
            <a href="/auth/login.html" className="bs-navlink bs-navlink--quiet">
              <span>Sign in</span>
            </a>
            <a href="/auth/signup.html" className="bs-nav-cta">
              <span>Get on the platform</span>
              <svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true">
                <path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </a>
          </div>
        ) : (
          <span className="bs-status"><i></i> Embargoed research index</span>
        )}
        <ThemeToggle theme={theme} onToggle={onToggle} className="bs-themetoggle" />
      </div>
    </header>
  );
}

// Mega-menu for the Products nav item; gated by FEATURES.dropdownNav.
function BSNavProductsDropdown({ item }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onEsc = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("click", onDoc);
    document.addEventListener("keydown", onEsc);
    return () => { document.removeEventListener("click", onDoc); document.removeEventListener("keydown", onEsc); };
  }, []);
  return (
    <div className={`bs-navdrop ${open ? "is-open" : ""}`} ref={ref}
         onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
      <a href={item.href} className="bs-navlink"
         onClick={(e) => { e.preventDefault(); setOpen((v) => !v); }}>
        <span>{item.label}</span>
        <svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true" className="bs-navdrop-caret">
          <path d="M3 4.5L6 8l3-3.5" fill="none" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </a>
      <div className="bs-navdrop-panel" role="menu">
        <div className="bs-navdrop-grid">
          {PRODUCTS.map((p, i) => (
            <a key={p.id} href={`/product.html?id=${p.id}`} className="bs-navdrop-item" role="menuitem">
              <span className="bs-navdrop-item-num">§ {String(i + 1).padStart(2, "0")}</span>
              <span className="bs-navdrop-item-name">{p.short}</span>
              <span className="bs-navdrop-item-eye">{p.eyebrow}</span>
            </a>
          ))}
        </div>
        <div className="bs-navdrop-foot">
          <a href="/#products">All products · overview →</a>
          <a href="/research.html">Research index →</a>
        </div>
      </div>
    </div>
  );
}

function BSFooter() {
  const features = window.FEATURES || {};
  return (
    <footer className="bs-footer">
      <div className="bs-foot-mark">
        <div className="bs-wordmark bs-wordmark-lg">{SITE.name}</div>
        <div className="bs-foot-sub">{SITE.tagline}</div>
        {features.liveStatus && (
          <div className="bs-foot-status">
            <span className="bs-foot-status-dot" />
            <span>All systems operational</span>
            <a href="https://status.blockscanner.org">status →</a>
          </div>
        )}
      </div>
      <div className="bs-foot-cols">
        <div className="bs-foot-col">
          <span className="bs-foot-col-label">Products</span>
          {PRODUCTS.map((p) => (
            <a key={p.id} href={`/product.html?id=${p.id}`}>{p.short}</a>
          ))}
        </div>
        <div className="bs-foot-col">
          <span className="bs-foot-col-label">Pages</span>
          <a href="/about.html">About</a>
          <a href="/research.html">Research</a>
          <a href="/case-studies.html">Engagements</a>
          <a href="/disclose.html">Disclose</a>
          <a href="/waitlist.html">Early access</a>
        </div>
        <div className="bs-foot-col">
          <span className="bs-foot-col-label">Reach</span>
          <a href={`mailto:${SITE.email}`}>{SITE.email}</a>
          <span>{SITE.domain}</span>
          {features.auth && <a href="/auth/login.html">Sign in</a>}
        </div>
      </div>
      <div className="bs-foot-rule" />
      <div className="bs-foot-fine">
        <span>© 2026 · {SITE.name} · Established {SITE.established}</span>
        <span className="bs-foot-legal">
          <a href="/privacy.html">Privacy</a>
          <a href="/terms.html">Terms</a>
        </span>
      </div>
    </footer>
  );
}

// ───────────────────────────────────────────────────────── Landing sections

function BSHero() {
  return (
    <section className="bs-hero" id="top">
      <p className="bs-eyebrow" data-reveal>{HERO.eyebrow}</p>
      <h1 className="bs-h1" data-reveal style={{ "--reveal-delay": "60ms" }}>
        {HERO.headline.split("\n").map((line, i, arr) => (
          <React.Fragment key={i}>
            <span className="bs-h1-line">{line}</span>
            {i < arr.length - 1 && <br />}
          </React.Fragment>
        ))}
      </h1>
      <p className="bs-lede" data-reveal style={{ "--reveal-delay": "140ms" }}>
        {HERO.lede}
      </p>
      {HERO.ctas && (
        <div className="bs-cta-row" data-reveal style={{ "--reveal-delay": "220ms" }}>
          {HERO.ctas.map((c, i) => (
            <a key={c.href} href={c.href} className={i === 0 ? "bs-cta-link bs-cta-link--primary" : "bs-cta-link"}>
              <span>{c.label}</span>
              <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
                <path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </a>
          ))}
        </div>
      )}
      <dl className="bs-hero-meta" data-reveal style={{ "--reveal-delay": "300ms" }}>
        <div>
          <dt>Established</dt>
          <dd>{SITE.established}</dd>
        </div>
        <div>
          <dt>Domains</dt>
          <dd>{SITE.domains}</dd>
        </div>
        <div>
          <dt>Index</dt>
          <dd>{SITE.domain}</dd>
        </div>
      </dl>
    </section>
  );
}

function BSStatBand() {
  if (!STATS || !STATS.length) return null;
  const [openIdx, setOpenIdx] = React.useState(null);
  // The index whose content is currently rendered in the detail panel. Lags
  // behind openIdx by one animation when switching tiles so the panel can
  // close, swap content, and re-open — never instantly replacing.
  const [renderIdx, setRenderIdx] = React.useState(null);
  const timeoutRef = React.useRef(null);

  React.useEffect(() => {
    clearTimeout(timeoutRef.current);
    if (openIdx === null) {
      // closing — keep current content rendered until the close animation
      // finishes so users see what they had.
      timeoutRef.current = setTimeout(() => setRenderIdx(null), 420);
    } else if (renderIdx === null) {
      // opening fresh
      setRenderIdx(openIdx);
    } else if (renderIdx !== openIdx) {
      // switching: close first, then swap + open after the animation
      const prev = openIdx;
      setOpenIdx(null);
      timeoutRef.current = setTimeout(() => {
        setRenderIdx(prev);
        setOpenIdx(prev);
      }, 280);
    }
    return () => clearTimeout(timeoutRef.current);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [openIdx]);

  React.useEffect(() => {
    if (openIdx === null) return;
    const onKey = (e) => { if (e.key === "Escape") setOpenIdx(null); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [openIdx]);

  const toggle = (i) => setOpenIdx((cur) => (cur === i ? null : i));
  const isPanelOpen = openIdx !== null && renderIdx === openIdx;
  const rendered = renderIdx !== null ? STATS[renderIdx] : null;

  return (
    <div className="bs-statband-wrap">
      <section className="bs-statband" aria-label="Key numbers">
        {STATS.map((s, i) => (
          <button
            key={i}
            type="button"
            className={`bs-statband-cell ${openIdx === i ? "is-open" : ""}`}
            onClick={() => toggle(i)}
            aria-expanded={openIdx === i}
            data-reveal
            style={{ "--reveal-delay": `${i * 60}ms` }}
          >
            <span className="bs-statband-plus" aria-hidden="true">
              {openIdx === i ? "−" : "+"}
            </span>
            <span className="bs-statband-num">{s.value}</span>
            <span className="bs-statband-label">
              {s.label.split("\n").map((line, j, arr) => (
                <React.Fragment key={j}>
                  {line}{j < arr.length - 1 && <br />}
                </React.Fragment>
              ))}
            </span>
          </button>
        ))}
      </section>

      <div
        className={`bs-statband-detail ${isPanelOpen ? "is-open" : ""}`}
        aria-hidden={!isPanelOpen}
        style={{ "--col": renderIdx ?? 0 }}
      >
        <div className="bs-statband-detail-pointer" />
        <div className="bs-statband-detail-inner">
          {rendered && rendered.detail && (
            <React.Fragment>
              <div className="bs-statband-detail-l">
                <div className="bs-statband-detail-num">{rendered.value}</div>
                <h3 className="bs-statband-detail-title">{rendered.detail.title}</h3>
                <p className="bs-statband-detail-body">{rendered.detail.body}</p>
                {rendered.detail.cta && (
                  <a className="bs-cta-link bs-cta-link--primary bs-statband-detail-cta" href={rendered.detail.cta.href}>
                    <span>{rendered.detail.cta.label}</span>
                    <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
                      <path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
                    </svg>
                  </a>
                )}
              </div>
              <dl className="bs-statband-detail-facts">
                {rendered.detail.facts.map(([k, v]) => (
                  <div key={k}>
                    <dt>{k}</dt>
                    <dd>{v}</dd>
                  </div>
                ))}
              </dl>
              <button
                type="button"
                className="bs-statband-detail-close"
                onClick={() => setOpenIdx(null)}
                aria-label="Close"
              >
                ×
              </button>
            </React.Fragment>
          )}
        </div>
      </div>
    </div>
  );
}

function BSProductTOC() {
  return (
    <section className="bs-toc">
      <div className="bs-toc-head" data-reveal>
        <span className="bs-secindex">§ 01 — 05</span>
        <h2 className="bs-toc-title">Five engines.<br />One pipeline.</h2>
      </div>
      <ol className="bs-toc-list">
        {PRODUCTS.map((p, i) => (
          <li key={p.id} data-reveal style={{ "--reveal-delay": `${i * 40}ms` }}>
            <a href={`#${p.id}`}>
              <span className="bs-toc-num">§ {String(i + 1).padStart(2, "0")}</span>
              <span className="bs-toc-name">{p.short}</span>
              <span className="bs-toc-rule" />
              <span className="bs-toc-domain">{p.eyebrow}</span>
              <svg className="bs-toc-arrow" viewBox="0 0 12 12" width="12" height="12">
                <path d="M3 6h6M7 3l3 3-3 3" fill="none" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            </a>
          </li>
        ))}
      </ol>
    </section>
  );
}

function BSProduct({ product, index }) {
  const Figure = FIGURES[product.figureKey];
  const isFeatured = !!product.featureNumber;
  return (
    <article className={`bs-product ${isFeatured ? "bs-product--featured" : ""}`} id={product.id} data-reveal>
      <div className="bs-product-num">§ {String(index + 1).padStart(2, "0")}</div>
      <div className="bs-product-eyebrow">{product.eyebrow}</div>
      <h3 className="bs-product-title">{product.title}</h3>
      <div className="bs-product-body">
        <p>{product.body}</p>
        {product.footnote && (
          <p className="bs-product-footnote">{product.footnote}</p>
        )}
      </div>
      <dl className="bs-product-facts">
        {product.facts.map(([k, v]) => (
          <div key={k}>
            <dt>{k}</dt>
            <dd>{v}</dd>
          </div>
        ))}
      </dl>
      <figure className="bs-product-fig">
        {isFeatured ? (
          <div className="bs-feature">
            <div className="bs-feature-num">{product.featureNumber}</div>
            <div className="bs-feature-label">
              {product.featureLabel.split("\n").map((line, i, arr) => (
                <React.Fragment key={i}>
                  {line}{i < arr.length - 1 && <br />}
                </React.Fragment>
              ))}
            </div>
            <div className="bs-feature-chart">
              {Figure ? <Figure /> : null}
            </div>
          </div>
        ) : (
          <div className="bs-fig-art">
            {Figure ? <Figure /> : null}
          </div>
        )}
        <figcaption>
          <span className="bs-fig-label">Fig. {index + 1}</span>
          <span>{product.caption}</span>
        </figcaption>
      </figure>
    </article>
  );
}

function BSProducts() {
  return (
    <section className="bs-products" id="products">
      {PRODUCTS.map((p, i) => (
        <BSProduct key={p.id} product={p} index={i} />
      ))}
    </section>
  );
}

function BSContact() {
  const [submitted, setSubmitted] = React.useState(false);
  const [error, setError] = React.useState(null);
  const onSubmit = async (e) => {
    e.preventDefault();
    setError(null);
    try {
      const form = new FormData(e.currentTarget);
      await submitForm("/api/contact", Object.fromEntries(form.entries()));
      setSubmitted(true);
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <section className="bs-contact" id="contact">
      <div className="bs-contact-head" data-reveal>
        <span className="bs-secindex">§ 06</span>
        <p className="bs-eyebrow">{CONTACT.eyebrow}</p>
        <h2 className="bs-h2">{CONTACT.headline}</h2>
        <p className="bs-contact-lede">{CONTACT.lede}</p>
      </div>

      {submitted ? (
        <div className="bs-form-success" data-reveal>
          Thanks — we'll be in touch within two business days.
          <br />
          For anything time-sensitive, write to <a href={`mailto:${SITE.email}`}>{SITE.email}</a>.
        </div>
      ) : (
        <form className="bs-form" onSubmit={onSubmit} data-reveal>
          <div className="bs-formrow">
            <label className="bs-formfield">
              <span>Name</span>
              <input name="name" type="text" required />
            </label>
            <label className="bs-formfield">
              <span>Email</span>
              <input name="email" type="email" required />
            </label>
          </div>
          <div className="bs-formrow">
            <label className="bs-formfield">
              <span>Organization</span>
              <input name="organization" type="text" />
            </label>
            <label className="bs-formfield">
              <span>You are a</span>
              <select name="org_type" defaultValue="">
                <option value="" disabled>Select…</option>
                {CONTACT.orgTypes.map((o) => <option key={o}>{o}</option>)}
              </select>
            </label>
          </div>
          <label className="bs-formfield bs-formfield-wide">
            <span>{CONTACT.promptText}</span>
            <textarea name="message" rows="5" required placeholder="A short paragraph is enough." />
          </label>
          <div className="bs-formactions">
            {error && <p className="bs-form-error">Could not submit: {error}</p>}
            <button type="submit" className="bs-formsubmit">
              <span>Send</span>
              <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
                <path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </button>
            <span className="bs-formalt">
              Or email <a href={`mailto:${SITE.email}`}>{SITE.email}</a>
            </span>
          </div>
        </form>
      )}
    </section>
  );
}

// ───────────────────────────────────────────────── Research-index sections

function BSResearchHero() {
  return (
    <section className="bs-rhero" id="top">
      <p className="bs-eyebrow" data-reveal>{RESEARCH.eyebrow}</p>
      <h1 className="bs-h1 bs-h1-tight" data-reveal style={{ "--reveal-delay": "60ms" }}>
        {RESEARCH.headline}
      </h1>
      <p className="bs-lede" data-reveal style={{ "--reveal-delay": "140ms" }}>{RESEARCH.lede}</p>
      <dl className="bs-hero-meta" data-reveal style={{ "--reveal-delay": "220ms" }}>
        <div><dt>Section</dt><dd>Research</dd></div>
        <div><dt>Scope</dt><dd>EVM · Solana</dd></div>
        <div><dt>Disclosure</dt><dd>coordinated</dd></div>
      </dl>
    </section>
  );
}

function BSResearchCounts() {
  return (
    <section className="bs-counts">
      <ol className="bs-counts-list">
        {RESEARCH.counts.map((c, i) => (
          <li key={i} data-reveal style={{ "--reveal-delay": `${i * 80}ms` }}>
            <div className="bs-count-num">{c.value}</div>
            <div className="bs-count-label">{c.label}</div>
          </li>
        ))}
      </ol>
    </section>
  );
}

function BSResearchIndex() {
  return (
    <section className="bs-rindex" id="index">
      <header className="bs-rindex-head" data-reveal>
        <span className="bs-secindex">§ index</span>
        <h2 className="bs-h2">Class index</h2>
        <p className="bs-contact-lede">
          Anonymized listing of identified classes. Full details are available
          to verification partners under signed NDA.
        </p>
      </header>
      <table className="bs-table" data-reveal>
        <thead>
          <tr>
            <th>Ref.</th>
            <th>Domain</th>
            <th>Family</th>
            <th>Discovered</th>
            <th>Status</th>
            <th>Affected</th>
          </tr>
        </thead>
        <tbody>
          {RESEARCH.rows.map((r) => (
            <tr key={r.ref}>
              <td className="bs-mono">{r.ref}</td>
              <td>{r.domain}</td>
              <td>{r.family}</td>
              <td className="bs-mono">{r.discovered}</td>
              <td>
                <span className={`bs-status-pill bs-status--${r.status}`}>
                  {r.status}
                </span>
              </td>
              <td>{r.protocols}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </section>
  );
}

function BSDisclosure() {
  const d = RESEARCH.disclosure;
  return (
    <section className="bs-disclosure" id="disclosure">
      <header className="bs-secthead" data-reveal>
        <span className="bs-secindex">§ policy</span>
        <h2 className="bs-h2">{d.heading}</h2>
      </header>
      <div className="bs-disclosure-grid">
        <p className="bs-disclosure-body" data-reveal>{d.body}</p>
        <ol className="bs-steps">
          {d.points.map(([k, v], i) => (
            <li key={i} data-reveal style={{ "--reveal-delay": `${i * 50}ms` }}>
              <span className="bs-step-num">{k}</span>
              <span className="bs-step-body">{v}</span>
            </li>
          ))}
        </ol>
      </div>
    </section>
  );
}

function BSResearchAccess() {
  const a = RESEARCH.access;
  return (
    <section className="bs-access" id="access">
      <header className="bs-secthead" data-reveal>
        <span className="bs-secindex">§ access</span>
        <h2 className="bs-h2">{a.heading}</h2>
      </header>
      <p className="bs-access-body" data-reveal>{a.body}</p>
      <a className="bs-cta" href="/#contact" data-reveal>
        <span>Request scoped access</span>
        <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
          <path d="M3 8h10M9 4l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </a>
    </section>
  );
}

Object.assign(window, {
  useReveal, useAnchorScroll,
  BSHeader, BSFooter, BSHero, BSStatBand, BSProductTOC, BSProducts, BSProduct, BSContact,
  BSResearchHero, BSResearchCounts, BSResearchIndex, BSDisclosure, BSResearchAccess,
});
