/* Main app: routing, theme, post rendering (KaTeX + highlight.js), TOC + progress. */
const { useState: uState, useEffect: uEffect, useRef: uRef, useMemo } = React;

/* ----- hash routing ----- */
function parseHash() {
  const h = (location.hash || "#/").replace(/^#/, "");
  const parts = h.split("/").filter(Boolean);
  if (parts.length === 0) return { name: "home" };
  if (parts[0] === "post" && parts[1]) return { name: "post", id: parts[1] };
  if (parts[0] === "blog") return { name: "blog" };
  if (parts[0] === "pubs") return { name: "pubs" };
  return { name: "notfound", path: location.pathname + location.search + location.hash };
}
function routeToHash(r) {
  if (r.name === "post") return "#/post/" + r.id;
  if (r.name === "blog") return "#/blog";
  if (r.name === "pubs") return "#/pubs";
  return "#/";
}
function notFoundSource(fallback) {
  return fallback || (location.pathname + location.search + location.hash);
}
function redirectTo404(source) {
  const from = encodeURIComponent(notFoundSource(source));
  location.replace("/404.html?from=" + from);
}

/* ===== Home ===== */
function HomeView({ data, go }) {
  const recent = data.posts.slice(0, 3);
  return (
    <div className="view">
      <Hero profile={data.profile} links={data.quickLinks} />

      <hr className="divider" />
      <section className="block wrap">
        <div className="sec-head">
          <div>
            <div className="eyebrow">Research</div>
            <h2 style={{ marginTop: ".6rem" }}>Selected publications</h2>
          </div>
          <a className="sec-link" onClick={() => go({ name: "pubs" })}>All work <Ic.arrow style={{ width: 15, height: 15 }} /></a>
        </div>
        <div className="pub-list">
          {data.publications.map((p) => <PubCard key={p.id} pub={p} go={go} />)}
        </div>
      </section>

      <hr className="divider" />
      <section className="block wrap">
        <div className="sec-head">
          <div>
            <div className="eyebrow">Writing</div>
            <h2 style={{ marginTop: ".6rem" }}>Recent posts</h2>
          </div>
          <a className="sec-link" onClick={() => go({ name: "blog" })}>All posts <Ic.arrow style={{ width: 15, height: 15 }} /></a>
        </div>
        <div className="posts">
          {recent.map((p) => <PostRow key={p.id} post={p} go={go} />)}
        </div>
      </section>
    </div>
  );
}

/* ===== Blog index with tag filtering ===== */
function BlogView({ data, go }) {
  const [tag, setTag] = uState("all");
  const [q, setQ] = uState("");
  const allTags = useMemo(() => {
    const s = new Set();
    data.posts.forEach((p) => p.tags.forEach((t) => s.add(t)));
    return ["all", ...Array.from(s)];
  }, [data.posts]);
  const shown = data.posts.filter((p) => {
    const tagOk = tag === "all" || p.tags.includes(tag);
    const qq = q.trim().toLowerCase();
    const qOk = !qq || (p.title + " " + p.blurb + " " + p.tags.join(" ")).toLowerCase().includes(qq);
    return tagOk && qOk;
  });
  return (
    <div className="view">
      <section className="page-head wrap">
        <div className="eyebrow">Blog hub</div>
        <h1>Writing</h1>
        <p>Could be notes, paper walkthroughs, or anything interesting that appears random.</p>
        <div className="filter-bar">
          <span className="lbl">filter:</span>
          {allTags.map((t) => (
            <span key={t} className={"chip" + (tag === t ? " on" : "")} onClick={() => setTag(t)}>{t === "all" ? "all" : "#" + t}</span>
          ))}
          <div style={{ marginLeft: "auto", position: "relative", display: "flex", alignItems: "center" }}>
            <span style={{ position: "absolute", left: 10, color: "var(--faint)", display: "flex" }}><Ic.search style={{ width: 15, height: 15 }} /></span>
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="search…"
              style={{ font: "inherit", fontSize: ".85rem", padding: ".4rem .6rem .4rem 2rem", borderRadius: 9, border: "1px solid var(--border)", background: "var(--surface)", color: "var(--text)", width: 180 }} />
          </div>
        </div>
      </section>
      <section className="block wrap" style={{ paddingTop: "1rem" }}>
        {shown.length ? (
          <div className="card-grid">{shown.map((p) => <PCard key={p.id} post={p} go={go} />)}</div>
        ) : (
          <p className="muted mono" style={{ padding: "2rem 0" }}>No posts match that filter.</p>
        )}
      </section>
    </div>
  );
}

/* ===== Publications page ===== */
function PubsView({ data, go }) {
  return (
    <div className="view">
      <section className="page-head wrap">
        <div className="eyebrow">Research</div>
        <h1>Publications</h1>
        <p>Selected works of mine -- milestones for me.</p>
      </section>
      <section className="block wrap" style={{ paddingTop: "1rem" }}>
        <div className="pub-list">{data.publications.map((p) => <PubCard key={p.id} pub={p} go={go} />)}</div>
      </section>
    </div>
  );
}

function NotFoundRedirect({ path }) {
  uEffect(() => { redirectTo404(path); }, [path]);
  return null;
}

/* ===== Post / article view ===== */
function PostView({ data, go, id }) {
  const allPosts = [...(data.posts || []), ...(data.hiddenPosts || [])];
  const post = allPosts.find((p) => p.id === id);
  const bodyRef = uRef(null);
  const [toc, setToc] = uState([]);
  const [activeId, setActiveId] = uState(null);

  // render body once
  uEffect(() => {
    if (!post) return;
    const el = bodyRef.current;
    let cancelled = false;

    const enhanceArticle = (html) => {
      if (cancelled) return;
      el.innerHTML = html;

      // build TOC from h2[data-toc], assign ids
      const heads = Array.from(el.querySelectorAll("h2[data-toc]"));
      const items = heads.map((h, i) => {
        const slug = "s-" + i + "-" + (h.getAttribute("data-toc") || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
        h.id = slug;
        return { id: slug, label: h.getAttribute("data-toc") };
      });
      setToc(items);

      // KaTeX
      if (window.renderMathInElement) {
        try {
          window.renderMathInElement(el, {
            delimiters: [
              { left: "$$", right: "$$", display: true },
              { left: "$", right: "$", display: false },
            ],
            throwOnError: false,
          });
        } catch (e) {}
      }
      // highlight.js
      if (window.hljs) {
        el.querySelectorAll("pre code").forEach((b) => { try { window.hljs.highlightElement(b); } catch (e) {} });
      }
      // copy buttons
      el.querySelectorAll("[data-copy]").forEach((btn) => {
        btn.addEventListener("click", () => {
          const pre = btn.closest(".code-head")?.nextElementSibling;
          const code = pre?.innerText || "";
          navigator.clipboard?.writeText(code);
          const orig = btn.innerHTML;
          btn.innerHTML = "✓ copied";
          setTimeout(() => { btn.innerHTML = orig; }, 1400);
        });
      });
      window.scrollTo(0, 0);
    };

    setToc([]);
    setActiveId(null);
    el.innerHTML = '<p class="muted mono" style="padding: 1rem 0">Loading post...</p>';
    const bodyPath = post.bodyPath || (post.bodyKey ? "posts/" + post.bodyKey + ".html" : null);
    if (!bodyPath) {
      enhanceArticle("<p>Coming soon.</p>");
      return () => { cancelled = true; };
    }
    fetch(bodyPath)
      .then((res) => {
        if (!res.ok) throw new Error("Post body not found");
        return res.text();
      })
      .then(enhanceArticle)
      .catch(() => redirectTo404(location.pathname + location.search + "#/post/" + id));

    return () => { cancelled = true; };
  }, [id]);

  // scroll spy + progress
  uEffect(() => {
    const bar = document.getElementById("progress");
    if (bar) bar.classList.add("on");
    const onScroll = () => {
      const docH = document.documentElement.scrollHeight - window.innerHeight;
      const p = docH > 0 ? window.scrollY / docH : 0;
      if (bar) bar.style.transform = "scaleX(" + Math.min(1, Math.max(0, p)) + ")";
      // active heading
      const heads = toc.map((t) => document.getElementById(t.id)).filter(Boolean);
      let cur = heads[0]?.id || null;
      for (const h of heads) { if (h.getBoundingClientRect().top <= 120) cur = h.id; }
      setActiveId(cur);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => { window.removeEventListener("scroll", onScroll); if (bar) { bar.classList.remove("on"); bar.style.transform = "scaleX(0)"; } };
  }, [toc]);

  if (!post) return <NotFoundRedirect path={location.pathname + location.search + "#/post/" + id} />;

  const isPub = post.type === "Publication";
  const isHidden = !!post.hidden;
  const backRoute = isHidden ? "home" : (isPub ? "pubs" : "blog");
  const backLabel = isHidden ? "Home" : (isPub ? "Publications" : "Writing");
  const footLabel = isHidden ? "Home" : ("All " + (isPub ? "publications" : "posts"));
  return (
    <div className="view">
      <div className="wrap article-shell">
        <aside className="article-toc">
          {toc.length > 0 && <>
            <div className="toc-label">On this page</div>
            <ul className="toc">
              {toc.map((t) => (
                <li key={t.id}><a className={activeId === t.id ? "active" : ""} href={"#" + t.id}
                  onClick={(e) => { e.preventDefault(); document.getElementById(t.id)?.scrollIntoView({ behavior: "smooth", block: "start" }); }}>{t.label}</a></li>
              ))}
            </ul>
          </>}
        </aside>

        <main className="article-main">
          <a className="back-link" onClick={() => go({ name: backRoute })}><Ic.back style={{ width: 15, height: 15 }} />{backLabel}</a>
          <header className="article-head">
            <span className={"post-row__type" + (isPub ? " pub-type" : "")} style={{ fontSize: ".74rem" }}>{post.type}</span>
            <h1>{post.title}</h1>
            <div className="article-meta">
              <span>{fmtDate(post.date)}</span><span className="dot"></span>
              <span style={{ display: "inline-flex", alignItems: "center", gap: ".3rem" }}><Ic.clock style={{ width: 13, height: 13 }} />{post.readMin} min read</span>
              {post.venue && <><span className="dot"></span><span className="venue">{post.venue}</span></>}
            </div>
            {post.authors && <p className="article-authors" dangerouslySetInnerHTML={{ __html: "<b>Authors:</b> " + post.authors }} />}
            <div className="post-row__tags" style={{ marginTop: ".9rem" }}>
              {post.tags.map((t) => <span key={t} className="chip">#{t}</span>)}
            </div>
          </header>

          <article ref={bodyRef} className="prose"></article>

          <div className="article-foot">
            <a className="btn btn--ghost" onClick={() => go({ name: backRoute })}><Ic.back style={{ width: 15, height: 15 }} />{footLabel}</a>
            {post.arxiv && <a className="btn" href={post.arxiv} target="_blank" rel="noopener"><Ic.ext/>Read on arXiv</a>}
          </div>
        </main>
        <div className="article-aside-r"></div>
      </div>
    </div>
  );
}

/* ===== App shell ===== */
function App() {
  const data = window.DATA;
  const [route, setRoute] = uState(parseHash());
  const [theme, setTheme] = uState(() => {
    const saved = localStorage.getItem("xw-theme");
    if (saved) return saved;
    return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
  });
  const [egg, setEgg] = uState(false);

  uEffect(() => {
    document.documentElement.setAttribute("data-theme", theme);
    localStorage.setItem("xw-theme", theme);
  }, [theme]);

  uEffect(() => {
    const onHash = () => setRoute(parseHash());
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);

  const go = (r) => { location.hash = routeToHash(r); window.scrollTo(0, 0); };

  // easter egg: type the motto
  uEffect(() => {
    let buf = "";
    const target = "keep the beginner's mind";
    const onKey = (e) => {
      if (e.key.length === 1) buf = (buf + e.key.toLowerCase()).slice(-target.length);
      if (buf === target) { setEgg(true); buf = ""; }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  let view;
  if (route.name === "home") view = <HomeView data={data} go={go} />;
  else if (route.name === "blog") view = <BlogView data={data} go={go} />;
  else if (route.name === "pubs") view = <PubsView data={data} go={go} />;
  else if (route.name === "post") view = <PostView data={data} go={go} id={route.id} />;
  else view = <NotFoundRedirect path={route.path} />;

  return (
    <>
      <Nav route={route} go={go} theme={theme} toggleTheme={() => setTheme((t) => (t === "dark" ? "light" : "dark"))} />
      {view}
      <Footer profile={data.profile} />
      {egg && <EasterEgg onClose={() => setEgg(false)} />}
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
