# -*- coding: utf-8 -*-
"""
生成专业排版的电子书 HTML（单文件，内嵌 base64 图片）
设计系统参考：project_1/spatial-niche-tutorial 的电子书
（衬线书籍字体 + CSS 变量双主题 + 侧边栏导航 + 复制按钮 + 封面 + 返回顶部）

流程：
  1. make_book 生成 build/book.md
  2. pandoc --to html5 生成 body（含图片相对路径）
  3. Python 把图片替换为 base64 内嵌
  4. 套用电子书模板（设计系统 v2）
输出：细胞生物学方向生物信息学课题教程.html
"""
import base64
import io
import os
import re
import subprocess
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BUILD = os.path.join(ROOT, "build")
OUT = os.path.join(ROOT, "细胞生物学方向生物信息学课题教程.html")

sys.path.insert(0, os.path.join(ROOT, "code"))
import make_book
make_book.main()

# ---------- 1. pandoc 生成 HTML body ----------
cmd = [
    "pandoc", os.path.join(BUILD, "book.md"),
    "--to", "html5",
    "--toc", "--toc-depth=2",
    "--standalone", "--section-divs",
    "--metadata", "lang=zh-CN",
    "--metadata", "title=细胞生物学方向·生物信息学课题教程",
    "--syntax-highlighting=tango",
    "--resource-path=.",
    "-o", os.path.join(BUILD, "book_body.html"),
]
print(">> pandoc html")
r = subprocess.run(cmd, cwd=ROOT, capture_output=True)
if r.returncode != 0:
    print("pandoc FAILED:", r.stderr.decode("utf-8", errors="replace")[-800:])
    sys.exit(1)

html = io.open(os.path.join(BUILD, "book_body.html"), encoding="utf-8").read()

# ---------- 2. 图片 base64 内嵌 ----------
def embed_images(text, base_dir):
    def repl_tag(m):
        tag = m.group(0)
        src_m = re.search(r'src="([^"]+)"', tag, re.S)
        alt_m = re.search(r'alt="([^"]*)"', tag, re.S)
        if not src_m:
            return tag
        src = src_m.group(1)
        alt = alt_m.group(1) if alt_m else ""
        if src.startswith("http"):
            return tag
        path = os.path.join(base_dir, src)
        if not os.path.exists(path):
            print("WARN image missing:", path)
            return tag
        with open(path, "rb") as fh:
            b64 = base64.b64encode(fh.read()).decode("ascii")
        ext = os.path.splitext(path)[1].lower()
        mime = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
                ".gif": "image/gif", ".svg": "image/svg+xml"}.get(ext, "image/png")
        return f'<img src="data:{mime};base64,{b64}" alt="{alt}"/>'
    return re.sub(r'<img[^>]*src="[^"]+"[^>]*/?>', repl_tag, text, flags=re.S)

html = embed_images(html, ROOT)
n_img = len(re.findall(r"data:image", html))
print(f"内嵌图片: {n_img} 张")

# ---------- 3. 提取 body 与 TOC ----------
body_match = re.search(r"<body>(.*)</body>", html, re.S)
body = body_match.group(1) if body_match else html

toc_match = re.search(r'<nav id="TOC"[^>]*>(.*?)</nav>', body, re.S)
toc_html = toc_match.group(1) if toc_match else ""
body_no_toc = body
if toc_match:
    body_no_toc = body.replace(toc_match.group(0), "")

# ---------- 4. 封面 ----------
cover = """<div class="cover" id="cover">
  <div class="series">生物信息学系列教程</div>
  <div class="rule"></div>
  <h1 class="title">细胞生物学方向<br/>生物信息学课题教程</h1>
  <div class="subtitle">基于公共数据库的焦亡基因 × 肝细胞癌研究全流程</div>
  <div class="en">Cell Biology × Bioinformatics Tutorial<br/>Pyroptosis &amp; Hepatocellular Carcinoma</div>
  <div class="tagline">零基础入门 · 公共数据库 · 图文并茂 · 可发表</div>
  <div class="meta">第 1 版 · 2025 年</div>
</div>"""

toc_section = f"""<div class="book-toc" id="book-toc">
  <h2 class="toc-heading">目录</h2>
  {toc_html}
</div>"""

# ---------- 5. 模板（设计系统 v2，参考 spatial-niche-tutorial） ----------
template = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>细胞生物学方向 · 生物信息学课题教程</title>
<meta name="description" content="面向零基础新手的生信课题全流程：焦亡×肝癌×公共数据库。选题设计、数据获取、差异表达、功能富集、PPI 网络、生存分析、论文写作。"/>
<script>
/* 首屏前应用主题，避免闪烁 */
(function () {
  try {
    var t = localStorage.getItem("cellbio-tutorial-theme");
    if (!t) t = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
    document.documentElement.setAttribute("data-theme", t);
  } catch (e) {}
})();
</script>
<style type="text/css">
:root {
--bg: #faf9f7;
--fg: #2c3e50;
--muted: #7f8c8d;
--border: #e3e0da;
--panel: #ffffff;
--code-bg: #f6f8fa;
--code-fg: #24292f;
--link: #0a5aa8;
--accent: #1F4E79;
--accent-soft: #eaf1f7;
--shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
--sidebar-w: 300px;
}
[data-theme="dark"] {
--bg: #16181d;
--fg: #d8dde3;
--muted: #8a919c;
--border: #2a2e37;
--panel: #1d2128;
--code-bg: #21252c;
--code-fg: #d4dbe4;
--link: #6cb2e8;
--accent: #7fb3d9;
--accent-soft: #1e2b38;
--shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font-family: Georgia, "Times New Roman", "Noto Serif CJK SC", "Source Han Serif SC",
"Songti SC", STSong, SimSun, serif;
font-size: 16.5px;
line-height: 1.85;
transition: background 0.25s, color 0.25s;
}
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- 顶栏 ---------- */
.topbar {
position: fixed;
top: 0; left: 0; right: 0;
height: 52px;
background: var(--panel);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 12px;
padding: 0 16px;
z-index: 60;
box-shadow: var(--shadow);
}
.topbar .menu-btn {
display: none;
background: none; border: 1px solid var(--border);
border-radius: 6px; color: var(--fg);
font-size: 18px; cursor: pointer; width: 36px; height: 34px;
}
.topbar .title {
font-size: 15px;
font-weight: 700;
letter-spacing: 0.02em;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar .spacer { flex: 1; }
.topbar .icon-btn {
background: none; border: 1px solid var(--border);
border-radius: 6px; color: var(--fg);
font-size: 15px; cursor: pointer; width: 36px; height: 34px;
transition: background 0.15s;
}
.topbar .icon-btn:hover { background: var(--accent-soft); }

/* ---------- 侧边栏 ---------- */
.sidebar {
position: fixed;
top: 52px; bottom: 0; left: 0;
width: var(--sidebar-w);
background: var(--panel);
border-right: 1px solid var(--border);
overflow-y: auto;
padding: 18px 10px 40px;
z-index: 50;
}
.sidebar .toc-title {
font-size: 12px; color: var(--muted);
letter-spacing: 0.15em;
padding: 0 10px 10px;
border-bottom: 1px solid var(--border);
margin-bottom: 8px;
}
.sidebar ul { list-style: none; margin: 0; padding: 0; }
.sidebar li { margin: 0; }
.sidebar a {
display: block;
padding: 6px 10px;
border-radius: 6px;
color: var(--fg);
font-size: 14.5px;
border-left: 3px solid transparent;
transition: background 0.15s, border-color 0.15s;
}
.sidebar a:hover { background: var(--accent-soft); text-decoration: none; }
.sidebar .toc-chapter { font-weight: 700; margin-top: 4px; }
.sidebar .toc-section {
padding-left: 26px;
font-size: 13.5px;
color: var(--muted);
}
.sidebar .toc-section::before { content: "· "; }
.sidebar a.active {
background: var(--accent-soft);
border-left-color: var(--accent);
color: var(--accent);
}

/* ---------- 主内容 ---------- */
.main {
margin-left: var(--sidebar-w);
padding: 88px 40px 80px;
max-width: 880px;
}
.main > *:first-child { margin-top: 0; }

.main h1 {
font-family: Georgia, "Times New Roman", "Noto Serif CJK SC", "Source Han Serif SC",
"Songti SC", STSong, SimSun, serif;
font-size: 30px;
font-weight: 700;
line-height: 1.4;
margin: 2.2em 0 0.8em;
padding-bottom: 0.35em;
border-bottom: 3px solid var(--accent);
scroll-margin-top: 70px;
}
.main h2 {
font-size: 22px;
font-weight: 700;
margin: 1.8em 0 0.7em;
padding-left: 12px;
border-left: 4px solid var(--accent);
scroll-margin-top: 70px;
}
.main h3 {
font-size: 18.5px;
font-weight: 700;
margin: 1.4em 0 0.5em;
scroll-margin-top: 70px;
}
.main p { margin: 0.8em 0; text-align: justify; }
strong { color: var(--fg); }
.main ul, .main ol { padding-left: 1.8em; }
.main li { margin: 0.35em 0; }

.main blockquote {
margin: 1em 0;
padding: 10px 16px;
background: var(--accent-soft);
border-left: 4px solid var(--accent);
border-radius: 0 8px 8px 0;
color: var(--fg);
}
.main blockquote p { margin: 0.3em 0; }

code {
font-family: "Cascadia Code", "JetBrains Mono", Consolas, "SF Mono", Menlo,
"Noto Sans Mono CJK SC", "Sarasa Mono SC", "Microsoft YaHei", monospace;
font-size: 0.88em;
background: var(--code-bg);
color: var(--code-fg);
padding: 2px 5px;
border-radius: 4px;
border: 1px solid var(--border);
}
pre {
position: relative;
margin: 1.1em 0;
padding: 14px 16px;
background: var(--code-bg);
color: var(--code-fg);
border: 1px solid var(--border);
border-radius: 10px;
overflow-x: auto;
line-height: 1.6;
font-size: 13.5px;
}
pre code { background: none; border: none; padding: 0; font-size: inherit; color: inherit; }

.copy-btn {
position: absolute;
top: 8px; right: 8px;
border: 1px solid var(--border);
background: var(--panel);
color: var(--muted);
border-radius: 6px;
font-size: 12px;
padding: 3px 10px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s, background 0.15s;
z-index: 2;
}
pre:hover .copy-btn, .copy-btn:focus { opacity: 1; }
.copy-btn:hover { color: var(--fg); background: var(--accent-soft); }
.copy-btn.copied { color: #1e8449; border-color: #1e8449; }

.main table {
border-collapse: collapse;
margin: 1.1em auto;
width: 100%;
font-size: 14.5px;
display: block;
overflow-x: auto;
border: 1px solid var(--border);
border-radius: 8px;
}
.main table thead th {
background: var(--accent-soft);
color: var(--fg);
font-weight: 700;
border-bottom: 2px solid var(--accent);
padding: 9px 12px;
text-align: left;
}
.main table td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.main table tr:nth-child(even) td { background: rgba(128, 128, 128, 0.05); }
.main table tr:last-child td { border-bottom: none; }

/* ---------- 图片 ---------- */
figure { margin: 1.4em 0; text-align: center; }
figure img {
max-width: 100%;
max-height: 62vh;              /* 大图限制在视口高度内，避免一屏放不下 */
width: auto;
height: auto;
object-fit: contain;
border-radius: 8px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
cursor: zoom-in;               /* 提示可点击放大 */
transition: box-shadow 0.2s;
}
figure img:hover { box-shadow: 0 4px 18px rgba(0,0,0,0.18); }
.figcaption {
margin-top: 8px;
font-size: 13.5px;
color: var(--muted);
line-height: 1.7;
text-align: justify;
}
.figcaption .fig-label { font-weight: 700; color: var(--accent); }

/* 竖版大图（技术路线图等）进一步限宽，居中显示 */
figure img.portrait {
max-width: 68%;
max-height: 70vh;
}

/* ---------- Lightbox 全屏查看 ---------- */
.lightbox {
position: fixed; inset: 0;
background: rgba(0, 0, 0, 0.88);
display: none;
align-items: center;
justify-content: center;
z-index: 200;
cursor: zoom-out;
padding: 24px;
}
.lightbox.show { display: flex; }
.lightbox img {
max-width: 94vw;
max-height: 92vh;
object-fit: contain;
border-radius: 6px;
box-shadow: 0 8px 40px rgba(0,0,0,0.5);
}
.lightbox .lb-close {
position: absolute; top: 16px; right: 24px;
color: #fff; font-size: 34px; cursor: pointer; line-height: 1;
opacity: 0.85;
}
.lightbox .lb-close:hover { opacity: 1; }
.lightbox .lb-caption {
position: absolute; bottom: 14px; left: 0; right: 0;
text-align: center; color: #ddd; font-size: 14px; padding: 0 40px;
}

hr { border: none; border-top: 1px solid var(--border); margin: 2em 0; }

/* ---------- 目录页 ---------- */
.book-toc { max-width: 760px; margin: 0 auto 3em; padding: 0 8px; }
.book-toc .toc-heading {
font-size: 26px;
font-weight: 700;
color: var(--accent);
text-align: center;
margin: 0 0 1.2em;
border: none;
padding: 0;
}
.book-toc nav#TOC { padding: 0; }
.book-toc nav#TOC ul { list-style: none; margin: 0; padding: 0; }
.book-toc nav#TOC > ul > li { margin: 0.6em 0; }
.book-toc nav#TOC a { color: var(--fg); font-size: 16px; font-weight: 700; }
.book-toc nav#TOC a:hover { color: var(--accent); text-decoration: none; }
.book-toc nav#TOC ul ul { margin: 0.3em 0 0.3em 1.4em; }
.book-toc nav#TOC ul ul a { font-weight: 400; font-size: 14px; color: var(--muted); }

/* ---------- 返回顶部 ---------- */
.back-top {
position: fixed;
right: 22px; bottom: 22px;
width: 42px; height: 42px;
border-radius: 50%;
border: 1px solid var(--border);
background: var(--panel);
color: var(--fg);
font-size: 18px;
cursor: pointer;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 70;
}
.back-top.show { opacity: 1; pointer-events: auto; }
.back-top:hover { transform: translateY(-3px); }

/* ---------- 封面 ---------- */
.cover {
min-height: calc(100vh - 52px);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 60px 20px;
background:
radial-gradient(1200px 500px at 50% -10%, var(--accent-soft), transparent 70%),
var(--panel);
border-bottom: 1px solid var(--border);
}
.cover .series { font-size: 14px; letter-spacing: 0.3em; color: var(--muted); }
.cover .rule { width: 240px; border-top: 2px solid var(--accent); margin: 22px auto; }
.cover h1.title {
font-size: 42px;
font-weight: 700;
border: none;
margin: 0;
color: var(--fg);
scroll-margin-top: 0;
line-height: 1.35;
}
.cover .subtitle { font-size: 24px; color: var(--muted); margin-top: 14px; }
.cover .en {
font-size: 16px; color: var(--muted); font-style: italic;
margin-top: 22px; line-height: 1.7;
}
.cover .tagline {
margin-top: 34px; font-size: 15px; color: var(--accent); letter-spacing: 0.12em;
}
.cover .meta { margin-top: 46px; font-size: 15px; color: var(--muted); }

/* ---------- 页脚 ---------- */
.footer {
max-width: 880px;
margin: 0 auto;
padding: 2.5em 8px 1.5em;
text-align: center;
font-size: 13px;
color: var(--muted);
border-top: 1px solid var(--border);
}

/* ---------- 响应式 ---------- */
@media (max-width: 900px) {
.sidebar {
transform: translateX(-105%);
transition: transform 0.25s;
width: min(320px, 82vw);
box-shadow: var(--shadow);
}
.sidebar.open { transform: translateX(0); }
.topbar .menu-btn { display: block; }
.main { margin-left: 0; padding: 76px 18px 60px; }
.cover h1.title { font-size: 30px; }
.cover .subtitle { font-size: 19px; }
body.sidebar-open::after {
content: "";
position: fixed; inset: 0;
background: rgba(0,0,0,0.35);
z-index: 45;
}
}

/* ---------- 打印 ---------- */
@media print {
.topbar, .sidebar, .back-top, .copy-btn, .lightbox { display: none !important; }
.main { margin: 0; max-width: none; padding: 0; }
body { background: #fff; color: #000; }
pre { white-space: pre-wrap; word-break: break-all; }
.main table { display: table; }
.main h1, .main h2, .main h3 { break-after: avoid; }
figure { break-inside: avoid; }
figure img { max-height: none; max-width: 100%; }
}
</style>
</head>
<body>

<header class="topbar">
  <button class="menu-btn" id="menuBtn" title="目录">☰</button>
  <span class="title">细胞生物学方向 · 生物信息学课题教程</span>
  <span class="spacer"></span>
  <button class="icon-btn" id="themeBtn" title="切换主题">◐</button>
</header>

<aside class="sidebar" id="sidebar">
  <div class="toc-title">目 录</div>
  __SIDEBAR_TOC__
</aside>

<div class="main" id="main">
  __COVER__
  __TOC__
  <div class="content">
  __BODY__
  </div>
</div>

<div class="footer">
  细胞生物学方向 · 生物信息学课题教程 第 1 版 — 示例图为教学示意图（模拟数据）。
</div>

<button class="back-top" id="backTop" title="返回顶部">↑</button>

<!-- Lightbox -->
<div class="lightbox" id="lightbox">
  <span class="lb-close" id="lbClose">×</span>
  <img id="lbImg" src="" alt=""/>
  <div class="lb-caption" id="lbCaption"></div>
</div>

<script>
(function () {
  var sidebar = document.getElementById("sidebar");
  var menuBtn = document.getElementById("menuBtn");
  var main = document.getElementById("main");
  var backTop = document.getElementById("backTop");
  var themeBtn = document.getElementById("themeBtn");

  /* 侧边栏 TOC 分类：一级=章节（加粗），二级=小节（缩进） */
  (function () {
    var top = sidebar.querySelector("ul");
    if (!top) return;
    Array.prototype.forEach.call(top.children, function (li) {
      var a = li.querySelector(":scope > a");
      if (a) a.classList.add("toc-chapter");
      var sub = li.querySelector(":scope > ul");
      if (sub) sub.querySelectorAll("a").forEach(function (sa) { sa.classList.add("toc-section"); });
    });
  })();

  /* 主题切换 */
  function currentTheme() {
    return document.documentElement.getAttribute("data-theme") || "light";
  }
  themeBtn.addEventListener("click", function () {
    var next = currentTheme() === "dark" ? "light" : "dark";
    document.documentElement.setAttribute("data-theme", next);
    try { localStorage.setItem("cellbio-tutorial-theme", next); } catch (e) {}
  });

  /* 侧边栏 */
  menuBtn.addEventListener("click", function () {
    var isOpen = sidebar.classList.toggle("open");
    document.body.classList.toggle("sidebar-open", isOpen);
  });

  /* 滚动高亮当前章节 */
  var tocLinks = sidebar.querySelectorAll("a");
  var headings = [];
  tocLinks.forEach(function (a) {
    var id = a.getAttribute("href");
    if (id && id.charAt(0) === "#") {
      var el = document.querySelector(id);
      if (el) headings.push({ link: a, el: el });
    }
  });
  function onScroll() {
    var pos = window.scrollY + 90;
    var current = null;
    for (var i = 0; i < headings.length; i++) {
      if (headings[i].el.offsetTop <= pos) current = headings[i].link;
      else break;
    }
    tocLinks.forEach(function (a) { a.classList.remove("active"); });
    if (current) current.classList.add("active");
    backTop.classList.toggle("show", window.scrollY > 400);
  }
  window.addEventListener("scroll", onScroll);
  onScroll();

  /* 代码复制按钮 */
  document.querySelectorAll("pre").forEach(function (pre) {
    var btn = document.createElement("button");
    btn.className = "copy-btn";
    btn.textContent = "复制";
    btn.addEventListener("click", function () {
      var code = pre.querySelector("code");
      var text = code ? code.innerText : pre.innerText;
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function () {
          btn.textContent = "已复制";
          btn.classList.add("copied");
          setTimeout(function () { btn.textContent = "复制"; btn.classList.remove("copied"); }, 1500);
        });
      } else {
        var ta = document.createElement("textarea");
        ta.value = text; document.body.appendChild(ta); ta.select();
        document.execCommand("copy"); document.body.removeChild(ta);
        btn.textContent = "已复制";
        setTimeout(function () { btn.textContent = "复制"; }, 1500);
      }
    });
    pre.appendChild(btn);
  });

  /* 图片点击放大（Lightbox） */
  var lightbox = document.getElementById("lightbox");
  var lbImg = document.getElementById("lbImg");
  var lbCaption = document.getElementById("lbCaption");
  var lbClose = document.getElementById("lbClose");
  var lbFig = null;
  function openLightbox(img) {
    lbFig = img.closest("figure");
    lbImg.src = img.src;
    var cap = lbFig ? lbFig.querySelector(".figcaption") : null;
    lbCaption.textContent = cap ? cap.textContent.trim() : (img.alt || "");
    lightbox.classList.add("show");
    document.body.style.overflow = "hidden";
  }
  function closeLightbox() {
    lightbox.classList.remove("show");
    document.body.style.overflow = "";
    lbImg.src = "";
  }
  document.querySelectorAll("figure img").forEach(function (img) {
    img.addEventListener("click", function () { openLightbox(img); });
  });
  lbClose.addEventListener("click", closeLightbox);
  lightbox.addEventListener("click", function (e) {
    if (e.target === lightbox || e.target === lbImg) closeLightbox();
  });
  document.addEventListener("keydown", function (e) {
    if (e.key === "Escape") closeLightbox();
  });

  /* 竖版大图标记（宽高比 > 1.15 的图加 portrait 类） */
  document.querySelectorAll("figure img").forEach(function (img) {
    if (img.naturalWidth && img.naturalHeight && img.naturalHeight / img.naturalWidth > 1.15) {
      img.classList.add("portrait");
    }
  });
})();
</script>
</body>
</html>
"""

final_html = (
    template
    .replace("__SIDEBAR_TOC__", toc_html)
    .replace("__COVER__", cover)
    .replace("__TOC__", toc_section)
    .replace("__BODY__", body_no_toc)
)

with io.open(OUT, "w", encoding="utf-8", newline="\n") as fh:
    fh.write(final_html)

print(f"电子书已生成: {OUT}  ({os.path.getsize(OUT)/1024/1024:.1f} MB)")
