/* BlogPost — reading view.
   Renders post.content as a sequence of blocks, with:
   - Reading progress bar
   - Auto-generated table of contents (h2 blocks)
   - Prev / next post navigation
   - Inline markdown-lite (bold/em/links/code spans) inside paragraphs
*/

function BlogPost({ postId, onBack, onOpenPost, readingWidth }) {
  const posts = window.BLOG_POSTS;
  const idx = posts.findIndex(p => p.id === postId);
  const post = posts[idx] || posts[0];
  const prev = posts[idx + 1]; // older
  const next = posts[idx - 1]; // newer

  const articleRef = React.useRef(null);
  const [progress, setProgress] = React.useState(0);
  const [activeH, setActiveH] = React.useState(null);
  const [vw, setVw] = React.useState(window.innerWidth);
  React.useEffect(() => {
    const update = () => setVw(window.innerWidth);
    window.addEventListener('resize', update, { passive: true });
    return () => window.removeEventListener('resize', update);
  }, []);

  // Reading progress
  React.useEffect(() => {
    function update() {
      const el = articleRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const total = rect.height - window.innerHeight;
      const scrolled = Math.max(0, -rect.top);
      const pct = total > 0 ? Math.min(1, scrolled / total) : 0;
      setProgress(pct);
    }
    update();
    window.addEventListener("scroll", update, { passive: true });
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update);
      window.removeEventListener("resize", update);
    };
  }, [postId]);

  // Active TOC heading
  React.useEffect(() => {
    function update() {
      const headings = Array.from(document.querySelectorAll("[data-toc-anchor]"));
      let current = null;
      for (const h of headings) {
        const r = h.getBoundingClientRect();
        if (r.top < 120) current = h.dataset.tocAnchor;
        else break;
      }
      setActiveH(current);
    }
    update();
    window.addEventListener("scroll", update, { passive: true });
    return () => window.removeEventListener("scroll", update);
  }, [postId]);

  const toc = (post.content || [])
    .filter(b => b.t === "h2" || (b._type === "block" && b.style === "h2"))
    .map(b => b.t === "h2" ? b : { text: (b.children || []).map(s => s.text).join("") });

  const showTocSidebar = vw >= 1024 && toc.length >= 2;

  return (
    <React.Fragment>
      {/* Progress bar */}
      <div style={bpStyles.progressBar} aria-hidden="true">
        <div style={{ ...bpStyles.progressFill, width: (progress * 100).toFixed(1) + "%" }}></div>
      </div>

      <article ref={articleRef} style={{ ...bpStyles.article, maxWidth: showTocSidebar ? 1200 : readingWidth }}>
        <a href="#"
           onClick={(e) => { e.preventDefault(); onBack(); }}
           style={bpStyles.back}>
          <i data-lucide="arrow-left" style={{ width: 14, height: 14 }}></i>
          All posts
        </a>

        <header style={bpStyles.head}>
          <div style={bpStyles.meta}>
            <span>{post.date}</span>
            <span style={{ opacity: 0.4 }}>·</span>
            <span>{post.read} read</span>
            <span style={{ opacity: 0.4 }}>·</span>
            <span>by Spencer Voorhees</span>
          </div>
          <h1 style={bpStyles.title}>{post.title}</h1>
          <p style={bpStyles.lede}>{post.summary}</p>
          <div style={bpStyles.headTags}>
            {post.tags.map(t => <span key={t} className="tag">#{t}</span>)}
          </div>
        </header>

        {/* TOC + body */}
        <div style={{
          ...bpStyles.bodyGrid,
          gridTemplateColumns: showTocSidebar ? `minmax(0, ${readingWidth}px) 200px` : "minmax(0,1fr)",
        }}>
          <div className="prose" style={bpStyles.prose}>
            {(post.content || []).map((block, i) => <Block key={i} block={block} />)}

            <hr />
            <p style={bpStyles.colophonNote}>
              Comments, corrections, war stories — <a href="mailto:hello@spencervoorhees.com">drop me an email</a>. I update posts in place and credit corrections at the bottom.
            </p>
          </div>

          {showTocSidebar ? (
            <aside style={bpStyles.toc} aria-label="On this page">
              <div style={bpStyles.tocLabel}>On this page</div>
              <ul style={bpStyles.tocList}>
                {toc.map((b) => {
                  const slug = slugify(b.text);
                  const active = activeH === slug;
                  return (
                    <li key={slug}>
                      <a href={"#" + slug}
                         style={{
                           ...bpStyles.tocLink,
                           color: active ? "var(--accent)" : "var(--fg-tertiary)",
                           borderLeftColor: active ? "var(--accent)" : "transparent",
                         }}>
                        {b.text}
                      </a>
                    </li>
                  );
                })}
              </ul>
            </aside>
          ) : null}
        </div>

        {/* Prev / next nav */}
        <nav style={{ ...bpStyles.pager, gridTemplateColumns: vw < 600 ? "1fr" : "1fr 1fr" }} aria-label="More posts">
          {prev ? (
            <a href="#" onClick={(e) => { e.preventDefault(); onOpenPost(prev.id); }} style={{ ...bpStyles.pagerCard, textAlign: "left" }}>
              <span style={bpStyles.pagerLabel}>← Older</span>
              <span style={bpStyles.pagerTitle}>{prev.title}</span>
              <span style={bpStyles.pagerMeta}>{prev.date}</span>
            </a>
          ) : (vw >= 600 ? <span></span> : null)}
          {next ? (
            <a href="#" onClick={(e) => { e.preventDefault(); onOpenPost(next.id); }} style={{ ...bpStyles.pagerCard, textAlign: vw < 600 ? "left" : "right" }}>
              <span style={bpStyles.pagerLabel}>Newer →</span>
              <span style={bpStyles.pagerTitle}>{next.title}</span>
              <span style={bpStyles.pagerMeta}>{next.date}</span>
            </a>
          ) : (vw >= 600 ? <span></span> : null)}
        </nav>
      </article>
    </React.Fragment>
  );
}

function Block({ block }) {
  // ── Portable Text (from Sanity) ──────────────────────────────────────────
  if (block._type === "block") {
    if (block.style === "hr") return <hr />;
    const content = renderPTSpans(block.children || [], block.markDefs || []);
    if (block.style === "h2") {
      const slug = slugify((block.children || []).map(s => s.text).join(""));
      return (
        <h2 id={slug} data-toc-anchor={slug}>
          <a href={"#" + slug} style={h2AnchorStyle}>{content}</a>
        </h2>
      );
    }
    if (block.style === "blockquote") return <blockquote style={quoteStyle}>{content}</blockquote>;
    return <p>{content}</p>;
  }
  if (block._type === "codeBlock") {
    return (
      <div className="codeblock">
        <div className="codeblock__head">
          <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
            <span style={{ opacity: 0.6 }}>●</span>
            <span>{block.filename}</span>
          </div>
          <span>{block.language}</span>
        </div>
        <pre><code>{block.code}</code></pre>
      </div>
    );
  }
  if (block._type === "aside") {
    return (
      <aside style={asideStyle}>
        <div style={asideRule}></div>
        <div>{block.text}</div>
      </aside>
    );
  }

  // ── Legacy format (data.jsx static posts) ───────────────────────────────
  switch (block.t) {
    case "h2": {
      const slug = slugify(block.text);
      return (
        <h2 id={slug} data-toc-anchor={slug}>
          <a href={"#" + slug} style={h2AnchorStyle}>{block.text}</a>
        </h2>
      );
    }
    case "p":
      return <p style={block.muted ? { color: "var(--fg-tertiary)", fontSize: 14 } : undefined}>{renderInline(block.text)}</p>;
    case "code":
      return (
        <div className="codeblock">
          <div className="codeblock__head">
            <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
              <span style={{ opacity: 0.6 }}>●</span>
              <span>{block.filename}</span>
            </div>
            <span>{block.lang}</span>
          </div>
          <pre><code>{block.code}</code></pre>
        </div>
      );
    case "aside":
      return (
        <aside style={asideStyle}>
          <div style={asideRule}></div>
          <div>{renderInline(block.text)}</div>
        </aside>
      );
    case "hr":
      return <hr />;
    case "quote":
      return <blockquote style={quoteStyle}>{renderInline(block.text)}</blockquote>;
    default:
      return null;
  }
}

// --- Portable Text inline spans with marks ---
function renderPTSpans(children, markDefs) {
  return children.map((span, i) => {
    let node = span.text;
    for (const mark of (span.marks || [])) {
      if (mark === "strong") { node = <strong key={i}>{node}</strong>; }
      else if (mark === "em") { node = <em key={i}>{node}</em>; }
      else if (mark === "code") { node = <code key={i}>{node}</code>; }
      else {
        const def = markDefs.find(d => d._key === mark);
        if (def && def._type === "link") node = <a key={i} href={def.href}>{node}</a>;
      }
    }
    return node;
  });
}

// --- inline markdown: **bold**, *em*, `code`, [text](url) ---
function renderInline(text) {
  if (typeof text !== "string") return text;
  const tokens = [];
  let i = 0;
  const push = (n) => tokens.push(n);
  const re = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g;
  let m;
  let last = 0;
  while ((m = re.exec(text)) !== null) {
    if (m.index > last) push(text.slice(last, m.index));
    const tok = m[0];
    if (tok.startsWith("**")) push(<strong key={i++}>{tok.slice(2, -2)}</strong>);
    else if (tok.startsWith("`")) push(<code key={i++}>{tok.slice(1, -1)}</code>);
    else if (tok.startsWith("[")) {
      const txt = tok.match(/\[([^\]]+)\]/)[1];
      const url = tok.match(/\(([^)]+)\)/)[1];
      push(<a key={i++} href={url}>{txt}</a>);
    } else if (tok.startsWith("*")) push(<em key={i++}>{tok.slice(1, -1)}</em>);
    last = m.index + tok.length;
  }
  if (last < text.length) push(text.slice(last));
  return tokens;
}

function slugify(s) {
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
}

const h2AnchorStyle = {
  color: "inherit", textDecoration: "none",
};

const asideStyle = {
  display: "flex", gap: 16, alignItems: "stretch",
  margin: "32px 0", padding: "16px 18px",
  background: "var(--bg-subtle)",
  border: "1px solid var(--border-subtle)",
  borderRadius: 10,
  fontSize: 16, lineHeight: 1.55,
  color: "var(--fg-secondary)",
  fontStyle: "italic",
};
const asideRule = {
  width: 3, alignSelf: "stretch",
  background: "var(--accent)", borderRadius: 2, flexShrink: 0,
};
const quoteStyle = {
  margin: "32px 0", padding: "0 20px",
  borderLeft: "3px solid var(--border)",
  color: "var(--fg-secondary)", fontStyle: "italic",
};

const bpStyles = {
  progressBar: {
    position: "fixed", top: 56, left: 0, right: 0, height: 2,
    background: "transparent", zIndex: 40,
  },
  progressFill: {
    height: "100%", background: "var(--accent)",
    transition: "width 80ms linear",
  },
  article: { margin: "0 auto", padding: "56px max(20px, 5vw) 80px" },
  back: {
    display: "inline-flex", gap: 6, alignItems: "center",
    fontSize: 13, color: "var(--fg-tertiary)", textDecoration: "none",
    marginBottom: 40, fontWeight: 500,
  },
  head: { marginBottom: 40 },
  meta: {
    display: "flex", gap: 8, alignItems: "center",
    fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--fg-tertiary)",
    marginBottom: 20, flexWrap: "wrap",
  },
  title: {
    fontFamily: "var(--font-display)", fontSize: "clamp(28px, 5.5vw, 48px)", fontWeight: 600,
    letterSpacing: "-0.022em", lineHeight: 1.08, margin: 0, color: "var(--fg)",
    textWrap: "balance",
  },
  lede: {
    marginTop: 20, fontSize: "clamp(16px, 3.5vw, 21px)", lineHeight: 1.5, color: "var(--fg-secondary)",
    letterSpacing: "-0.011em", maxWidth: 620, textWrap: "pretty",
  },
  headTags: { display: "flex", gap: 6, marginTop: 24, flexWrap: "wrap" },
  bodyGrid: {
    display: "grid",
    gridTemplateColumns: "minmax(0, 1fr)",
    gap: 56,
    position: "relative",
  },
  prose: { fontSize: 18, lineHeight: 1.7, color: "var(--fg)" },
  toc: {
    position: "sticky", top: 88,
    alignSelf: "start",
    fontSize: 12,
    paddingTop: 4,
  },
  tocLabel: {
    fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.08em",
    textTransform: "uppercase", color: "var(--fg-quaternary)",
    marginBottom: 10,
  },
  tocList: { listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 2 },
  tocLink: {
    display: "block", textDecoration: "none",
    padding: "4px 12px",
    borderLeft: "2px solid transparent",
    transition: "color 160ms, border-color 160ms",
    lineHeight: 1.4, fontSize: 12,
  },
  colophonNote: { color: "var(--fg-tertiary)", fontSize: 14 },

  pager: {
    marginTop: 64, paddingTop: 32,
    borderTop: "1px solid var(--border-subtle)",
    display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16,
  },
  pagerCard: {
    display: "flex", flexDirection: "column", gap: 6,
    padding: "20px 22px",
    background: "var(--surface)",
    border: "1px solid var(--border)",
    borderRadius: 12,
    textDecoration: "none", color: "inherit",
    transition: "border-color 160ms, transform 220ms, box-shadow 220ms",
  },
  pagerLabel: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-tertiary)", letterSpacing: "0.03em" },
  pagerTitle: { fontFamily: "var(--font-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.011em", lineHeight: 1.3, color: "var(--fg)" },
  pagerMeta: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-quaternary)" },
};

window.BlogPost = BlogPost;
