# -*- coding: utf-8 -*-
"""学生版网页生成器：只有内容，不带任何"原页/叠字/纯文字"选项与侧栏。

每页：幻灯片整页图 + 上一页/下一页 + 第 N/M 页（页数 = 源课件页数）
目录页：缩略图 + 页码 + 标题 + 关键词搜索
问题页：题目仍按原页呈现，页面下方提供"查看参考答案"，**默认折叠**（点击才展开）

用法：python _build_student.py <_conv_work 目录> <输出目录> "<课程名>" "<章节名>"
     参考答案在 ANSWERS 里按页号配置。
"""
import json
import os
import shutil
import sys

ANSWERS = {
    57: {
        'q': '如果开发人员测试自己的产品，会有哪些障碍？TDD 又是如何克服这些障碍的？',
        'a': ['思维盲区：顺着自己的实现思路去测，容易漏掉"没想到"的路径和组合。',
              '心态偏差：倾向证明"我写的是对的"，而不是主动找错。',
              '独立性与时间不足：自己测自己，缺少第三方视角，且开发任务挤压测试时间。',
              'TDD 的克服方式：先把期望写成测试、再写实现——迫使开发者从需求与接口出发；'
              '测试先行形成自动回归网，改动后立刻暴露回归缺陷；用例即"可执行规格"，减少主观判断。'],
    },
    59: {
        'q': '提问、思考与练习（4 个问题）',
        'a': ['日常质量问题与缺陷类型：闪退/卡顿属性能与可靠性；数据丢失或计算错误属功能与数据缺陷；'
              '界面错位、字体乱码属兼容性与易用性问题；更新后旧功能失效属回归缺陷。',
              '正反思维的影响：正向思维按规格与流程验证"该做的做到了"；反向思维假设程序有错，'
              '专挑边界、异常输入与组合去证伪，用例更"刁钻"。两者结合才能既验证需求、又发现隐藏缺陷。',
              '测试与开发并行协作：需求与设计阶段测试即介入（测试左移），开发完成即测，'
              '缺陷早发现、修复成本低；例如需求评审时测试提出二义性、接口未实现先冻结契约与用例。',
              '测试与 SQA 的联系与区别：SQA 面向过程（规范、评审、度量、过程改进），'
              '测试面向产品（验证与确认、提供质量信息）；测试是 SQA 的重要手段之一，'
              'SQA 为测试提供流程与标准保障。'],
    },
}

CSS = """
:root{--ink:#1f2937;--navy:#1a365d;--blue:#2b6cb0;--line:#cbd5e0;--bg:#f5f7fa}
*{box-sizing:border-box}
body{margin:0;font-family:"Microsoft YaHei","PingFang SC","Hiragino Sans GB",sans-serif;color:var(--ink);background:var(--bg)}
header{display:flex;align-items:center;gap:14px;padding:8px 16px;background:var(--navy);color:#fff;position:sticky;top:0;z-index:20}
header .t{font-weight:700}
header .sp{flex:1}
header a{background:rgba(255,255,255,.14);color:#fff;border-radius:6px;padding:6px 12px;font-size:13px;text-decoration:none}
main{padding:16px;max-width:1280px;margin:0 auto}
.stage{position:relative;width:100%;aspect-ratio:16/9;background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden}
.stage img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#fff}
.pager{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:14px}
.pager a{background:var(--navy);color:#fff;text-decoration:none;padding:10px 20px;border-radius:8px;font-size:14px}
.pager a.dis{opacity:.35;pointer-events:none}
.pager .no{color:#4a5568;font-size:14px}
details.ans{margin-top:14px;background:#fff;border:1px solid var(--line);border-left:4px solid var(--blue);border-radius:0 8px 8px 0;padding:10px 14px}
details.ans summary{cursor:pointer;font-weight:700;color:var(--blue);font-size:15px;list-style:none}
details.ans summary::-webkit-details-marker{display:none}
details.ans summary::before{content:"▸ ";}
details.ans[open] summary::before{content:"▾ ";}
details.ans .q{font-size:14px;color:#4a5568;margin:8px 0 6px}
details.ans ol{margin:6px 0 2px;padding-left:22px;font-size:14px;line-height:1.75}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}
.card{background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden;text-decoration:none;color:inherit}
.card img{display:block;width:100%;aspect-ratio:16/9;object-fit:contain;background:#fafbfc}
.card .cap{padding:8px 10px;font-size:13px}
.card .no{display:inline-block;min-width:28px;color:var(--blue);font-weight:700}
.search{padding:16px 16px 0}
#q{width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:8px;font-size:14px;font-family:inherit}
"""

JS = """
(function(){
  var prev=document.querySelector('.pager a.prev'), next=document.querySelector('.pager a.next');
  document.addEventListener('keydown',function(e){
    if(e.target.tagName==='INPUT') return;
    if(e.key==='ArrowLeft'&&prev&&!prev.classList.contains('dis')) location.href=prev.href;
    if(e.key==='ArrowRight'&&next&&!next.classList.contains('dis')) location.href=next.href;
    if(e.key==='Home') location.href='index.html';
  });
  var lock=false;
  window.addEventListener('wheel',function(e){
    if(lock||Math.abs(e.deltaY)<18) return; lock=true; setTimeout(function(){lock=false;},450);
    if(e.deltaY>0&&next&&!next.classList.contains('dis')) location.href=next.href;
    if(e.deltaY<0&&prev&&!prev.classList.contains('dis')) location.href=prev.href;
  },{passive:true});
  var q=document.getElementById('q');
  if(q){var cards=[].slice.call(document.querySelectorAll('.card'));
    q.addEventListener('input',function(){var v=q.value.trim().toLowerCase();
      cards.forEach(function(c){var hay=((c.dataset.text||'')+' '+(c.dataset.title||'')).toLowerCase();
        c.style.display=(!v||hay.indexOf(v)>=0)?'':'none';});});}
})();
"""


def esc(s):
    return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')


def build(work, outdir, course, chapter, answers=None):
    answers = answers or {}
    data = json.load(open(os.path.join(work, '_data', 'slides.json'), encoding='utf-8'))
    slides, n = data['slides'], data['count']
    if os.path.isdir(outdir):
        shutil.rmtree(outdir)
    os.makedirs(os.path.join(outdir, 'assets'), exist_ok=True)
    page_src = os.path.join(work, 'assets', 'page')
    page_dst = os.path.join(outdir, 'assets', 'page')
    os.makedirs(page_dst, exist_ok=True)
    names = sorted(os.listdir(page_src))
    for k, name in enumerate(names, 1):
        shutil.copyfile(os.path.join(page_src, name),
                        os.path.join(page_dst, 'slide-%03d%s' % (k, os.path.splitext(name)[1])))
    open(os.path.join(outdir, 'app.css'), 'w', encoding='utf-8').write(CSS)
    open(os.path.join(outdir, 'app.js'), 'w', encoding='utf-8').write(JS)

    def head(title):
        return ('<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8">'
                '<meta name="viewport" content="width=device-width,initial-scale=1">'
                '<title>%s</title><link rel="stylesheet" href="app.css"></head><body>' % esc(title))

    for s in slides:
        i = s['index']
        p = [head('%s 第 %d 页' % (chapter, i)),
             '<header><span class="t">%s</span><span>%s</span><span class="sp"></span>'
             '<a href="index.html">目录</a></header><main>'
             % (esc(course), esc(chapter)),
             '<section class="stage"><img src="assets/page/slide-%03d.png" alt="第 %d 页"></section>' % (i, i)]
        if i in answers:
            a = answers[i]
            p.append('<details class="ans"><summary>查看参考答案</summary>'
                     '<div class="q">%s</div><ol>%s</ol></details>'
                     % (esc(a['q']), ''.join('<li>%s</li>' % esc(x) for x in a['a'])))
        p.append('</main><div class="pager" style="max-width:1280px;margin:0 auto;padding:0 16px 26px">')
        p.append('<a class="prev%s" href="%s">← 上一页</a>'
                 % (' dis' if i == 1 else '', 'index.html' if i == 1 else 'slide-%03d.html' % (i - 1)))
        p.append('<span class="no">第 %d / %d 页</span>' % (i, n))
        p.append('<a class="next%s" href="%s">下一页 →</a>'
                 % (' dis' if i == n else '', 'index.html' if i == n else 'slide-%03d.html' % (i + 1)))
        p.append('</div><script src="app.js"></script></body></html>')
        open(os.path.join(outdir, 'slide-%03d.html' % i), 'w', encoding='utf-8').write(''.join(p))

    idx = [head('%s %s' % (course, chapter)),
           '<header><span class="t">%s</span><span>%s</span><span class="sp"></span>'
           '<a href="/">课程主页</a></header>' % (esc(course), esc(chapter)),
           '<div class="search"><input id="q" placeholder="输入关键词搜索（如：缺陷、等价类、TDD）"></div>',
           '<div class="grid" style="padding:16px">']
    for s in slides:
        txt = ' '.join(t['text'] for t in s['texts'])
        idx.append('<a class="card" href="slide-%03d.html" data-title="%s" data-text="%s">'
                   '<img loading="lazy" src="assets/page/slide-%03d.png" alt="第 %d 页">'
                   '<div class="cap"><span class="no">%02d</span>%s</div></a>'
                   % (s['index'], esc(s['title']), esc(txt), s['index'], s['index'],
                      s['index'], esc(s['title'])))
    idx.append('</div><div style="padding:0 16px 28px;color:#718096;font-size:12px">'
               '共 %d 页。方向键或滚轮翻页；目录页可用关键词检索。</div>'
               '<script src="app.js"></script></body></html>' % n)
    open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8').write(''.join(idx))
    return n


if __name__ == '__main__':
    work, outdir = sys.argv[1], sys.argv[2]
    course = sys.argv[3] if len(sys.argv) > 3 else '软件测试课程设计'
    chapter = sys.argv[4] if len(sys.argv) > 4 else '第 1 章 引论'
    answers = {}
    if len(sys.argv) > 5 and os.path.exists(sys.argv[5]):
        answers = {int(k): v for k, v in json.load(open(sys.argv[5], encoding='utf-8')).items()}
    n = build(work, outdir, course, chapter, answers)
    print('学生版已生成 %d 页 + 目录（折叠答案 %d 处）-> %s' % (n, len(answers), outdir))
