# -*- coding: utf-8 -*-
"""讲稿 → 打印版 HTML 生成器（再由 _make_pdf.js 渲染为 A4 PDF）
用法：python3 _make_pdf.py
输出：_print/*.html（4 个版本：第1章、第2章、合订、备课批注版）
"""
import os, re, html as H, datetime

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, '_print')
os.makedirs(OUT, exist_ok=True)

CH = {
    1: '第1章-软件项目管理概述-讲稿.md',
    2: '第2章-软件项目启动-讲稿.md',
}
TITLE = {
    1: '第 1 章　软件项目管理概述',
    2: '第 2 章　软件项目启动',
}
SUBTITLE = {
    1: '华为鸿蒙 OS 开发项目管理实践案例 ｜ 第 1 周 · 2 学时（90 分钟）',
    2: '某大型连锁超市智能库存管理系统案例 ｜ 第 2 周 · 2 学时（90 分钟）',
}

LABELS = [
    ('口播', 'speak'), ('板书', 'board'), ('提问', 'q'), ('预设回答', 'ans'),
    ('易错点 / 考点', 'exam'), ('易错点/考点', 'exam'), ('过渡', 'trans'),
]
LABEL_RE = re.compile(r'^\*\*(口播|板书|提问|预设回答|易错点\s*/\s*考点|过渡)\*\*[：:]\s*(.*)$')
SZ_RE = re.compile(r'^\*\*【思政落点\s*(\d+)】\*\*\s*(.*)$')
ANCHOR_RE = re.compile(r'^###\s+【(\d+)\.(\d+)(?:\s*[–\-—]\s*\d+\.(\d+))?】\s*(.*)$')


def esc(s):
    return H.escape(s, quote=False)


def inline(s):
    s = esc(s)
    s = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', s)
    s = re.sub(r'(?<!\*)\*([^*\n]+?)\*(?!\*)', r'<em>\1</em>', s)
    s = re.sub(r'`([^`]+?)`', r'<code>\1</code>', s)
    return s


def render_table(lines):
    rows = [[c.strip() for c in l.strip().strip('|').split('|')] for l in lines]
    if len(rows) >= 2 and re.match(r'^[\s:\-]+$', rows[1][0] or '-'):
        head, body = rows[0], rows[2:]
    else:
        head, body = rows[0], rows[1:]
    out = ['<table><thead><tr>' + ''.join(f'<th>{inline(c)}</th>' for c in head) + '</tr></thead><tbody>']
    for r in body:
        out.append('<tr>' + ''.join(f'<td>{inline(c)}</td>' for c in r) + '</tr>')
    out.append('</tbody></table>')
    return ''.join(out)


def render_block(kind, label, lines):
    """把一个区块渲染为 HTML；lines 为原始 markdown 行"""
    if kind == 'speak':
        paras, buf = [], []
        for l in lines:
            if l.strip() == '':
                if buf:
                    paras.append(' '.join(buf)); buf = []
            else:
                buf.append(l.strip())
        if buf:
            paras.append(' '.join(buf))
        inner = ''.join(f'<p>{inline(p)}</p>' for p in paras)
        return f'<div class="blk speak"><div class="tag">口播·可照读</div>{inner}</div>'
    if kind == 'table':
        return render_table(lines)
    # 带标签的区块（板书/提问/预设回答/易错点/过渡/思政）
    body, items = [], []
    for l in lines:
        s = l.strip()
        if not s:
            continue
        if re.match(r'^[-*]\s+', s):
            items.append(re.sub(r'^[-*]\s+', '', s))
        elif re.match(r'^\d+[.、]\s*', s):
            items.append(re.sub(r'^\d+[.、]\s*', '', s))
        else:
            body.append(s)
    txt = '<br>'.join(inline(b) for b in body)
    if items:
        txt += '<ul>' + ''.join(f'<li>{inline(i)}</li>' for i in items) + '</ul>'
    lbl = f'<span class="tag">{esc(label)}</span>' if label else ''
    return f'<div class="blk {kind}">{lbl}{txt}</div>'


def parse_section(body):
    """把一页讲稿正文切成区块 HTML"""
    blocks, cur = [], None  # cur = [kind, label, [lines]]
    def flush():
        nonlocal cur
        if cur and (cur[2] or cur[0] != 'speak'):
            blocks.append(render_block(cur[0], cur[1], cur[2]))
        cur = None
    for raw in body.split('\n'):
        s = raw.rstrip()
        m_sz = SZ_RE.match(s.strip())
        m_lb = LABEL_RE.match(s.strip())
        if m_sz:
            flush()
            cur = ['sz', f'思政落点 {m_sz.group(1)}', [m_sz.group(2)]]
            continue
        if m_lb:
            flush()
            kind = dict(LABELS).get(m_lb.group(1).replace(' ', ''), 'q')
            kind = 'exam' if '易错' in m_lb.group(1) else kind
            cur = [kind, m_lb.group(1), [m_lb.group(2)]]
            continue
        if s.strip().startswith('|'):
            # 表格：并入当前块（若无块则新建）
            if cur is None:
                cur = ['table', '', []]
            if cur[0] != 'table':
                flush(); cur = ['table', '', []]
            cur[2].append(s)
            continue
        if s.strip() == '':
            if cur and cur[0] not in ('table',):
                cur[2].append('')
            continue
        if s.strip() == '---':
            flush(); continue
        if cur is None:
            cur = ['speak', '', []]
        cur[2].append(s)
    flush()
    return '\n'.join(blocks)


def convert(md, with_notes=False, book_title=''):
    """整章 markdown → 打印 HTML 正文"""
    out = []
    # 头部（第一个 ### 之前的内容）
    parts = re.split(r'^(###\s+【.*)$', md, flags=re.M)
    head_md, sections = parts[0], parts[1:]
    # 头部：只取标题与要点（教学目标/时间分配），其余以简表呈现
    for chunk in re.split(r'\n(?=##\s)', head_md):
        chunk = chunk.strip()
        if not chunk:
            continue
        if chunk.startswith('# '):
            for l in chunk.split('\n'):
                l = l.strip()
                if l.startswith('# '):
                    out.append(f'<h1 class="chap">{inline(l[2:])}</h1>')
                elif l.startswith('> '):
                    out.append(f'<p class="meta">{inline(l[2:])}</p>')
                elif l.startswith('---'):
                    continue
            continue
        lines = chunk.split('\n')
        title = re.sub(r'^#+\s*', '', lines[0])
        out.append(f'<h2>{inline(title)}</h2>')
        # 该块内的表格 / 引用 / 列表
        buf, i = [], 0
        rest = lines[1:]
        while i < len(rest):
            s = rest[i].strip()
            if s.startswith('|'):
                tbl = []
                while i < len(rest) and rest[i].strip().startswith('|'):
                    tbl.append(rest[i].strip()); i += 1
                out.append(render_table(tbl)); continue
            if s.startswith('>'):
                bq = []
                while i < len(rest) and rest[i].strip().startswith('>'):
                    bq.append(rest[i].strip()[1:].strip()); i += 1
                out.append('<div class="blk note">' + '<br>'.join(inline(b) for b in bq if b) + '</div>')
                continue
            if re.match(r'^[-*]\s+', s) or re.match(r'^\d+[.、]\s*', s):
                items = []
                while i < len(rest) and (re.match(r'^[-*]\s+', rest[i].strip()) or re.match(r'^\d+[.、]\s*', rest[i].strip())):
                    items.append(re.sub(r'^([-*]|\d+[.、])\s*', '', rest[i].strip())); i += 1
                out.append('<ul>' + ''.join(f'<li>{inline(x)}</li>' for x in items) + '</ul>')
                continue
            if s:
                buf.append(s)
            i += 1
        if buf:
            out.append('<p>' + '<br>'.join(inline(b) for b in buf) + '</p>')

    # 逐页
    for i in range(0, len(sections), 2):
        head, body = sections[i].strip(), sections[i + 1] if i + 1 < len(sections) else ''
        m = ANCHOR_RE.match(head)
        if not m:
            continue
        no = f'{m.group(1)}.{m.group(2)}'
        rng = f'{m.group(1)}.{m.group(2)}–{m.group(3)}' if m.group(3) else no
        title = m.group(4).strip()
        out.append(f'<h2 class="slide"><span class="pno">{esc(rng)}</span>{inline(title)}</h2>')
        out.append('<div class="slidebody">' + parse_section(body) + '</div>')
        if with_notes:
            out.append('<div class="notes"><span>✍️ 我的批注</span></div>')
    return '\n'.join(out)


CSS = """
@page { size: A4; margin: 15mm 13mm 17mm; }
* { box-sizing: border-box; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body { font-family: "Noto Sans CJK SC","Source Han Sans SC","WenQuanYi Zen Hei",sans-serif;
       font-size: 10pt; line-height: 1.62; color: #1a202c; margin: 0; }
h1.cover-title { font-size: 26pt; color: #1a365d; text-align: center; margin: 60mm 0 6pt; letter-spacing: 2pt; }
.cover-sub { text-align: center; color: #4a5568; font-size: 12pt; margin: 4pt 0; }
.cover-meta { text-align: center; color: #718096; font-size: 9.5pt; margin-top: 40mm; line-height: 1.9; }
.cover { break-after: page; }
h1.chap { font-size: 16pt; color: #1a365d; border-bottom: 2.5pt solid #c49340; padding-bottom: 4pt; margin: 0 0 10pt; break-before: page; }
h1.chap.first { break-before: auto; }
h2 { font-size: 11.5pt; color: #1a365d; background: #eef4fb; border-left: 4pt solid #2b6cb0;
     padding: 3.5pt 8pt; margin: 12pt 0 6pt; break-after: avoid; }
h2.slide { display: flex; align-items: baseline; gap: 8pt; }
h2.slide .pno { font-family: "Source Han Mono SC", monospace; color: #b7791f; font-weight: 700; }
.slidebody { break-inside: auto; }
.blk { padding: 5pt 9pt; margin: 5pt 0; border-left: 3pt solid #cbd5e0; background: #fafbfc; break-inside: avoid; }
.blk .tag { display: inline-block; font-size: 7.5pt; font-weight: 700; color: #fff; background: #718096;
            padding: 0.5pt 5pt; border-radius: 7pt; margin-right: 6pt; vertical-align: 1pt; }
.blk.speak { background: #f6f9fc; border-left-color: #2b6cb0; }
.blk.speak .tag { background: #2b6cb0; }
.blk.speak p { margin: 3pt 0; }
.blk.board { background: #fffcf0; border-left-color: #c49340; }
.blk.board .tag { background: #b7791f; }
.blk.q { background: #f2f8ff; border-left-color: #3182ce; }
.blk.q .tag { background: #3182ce; }
.blk.ans { background: #f5fbf6; border-left-color: #38a169; }
.blk.ans .tag { background: #38a169; }
.blk.ans ul { margin: 3pt 0 0 0; padding-left: 16pt; }
.blk.ans li { margin: 2pt 0; }
.blk.exam { background: #fff6f6; border-left-color: #e53e3e; }
.blk.exam .tag { background: #e53e3e; }
.blk.sz { background: #faf6ff; border-left-color: #805ad5; }
.blk.sz .tag { background: #805ad5; }
.blk.trans { background: #fff; border-left-style: dashed; border-left-color: #a0aec0; color: #4a5568; font-style: italic; }
.blk.trans .tag { background: #a0aec0; }
.blk.note { background: #fffbeb; border-left-color: #c49340; }
p { margin: 5pt 0; }
p.meta { color: #4a5568; font-size: 9pt; margin: 2pt 0; }
ul, ol { margin: 4pt 0; padding-left: 18pt; }
li { margin: 2pt 0; }
strong { color: #1a365d; }
code { font-family: "Source Han Mono SC", monospace; background: #edf2f7; padding: 0 3pt; border-radius: 2pt; font-size: 9pt; }
table { width: 100%; border-collapse: collapse; font-size: 9pt; margin: 6pt 0; break-inside: auto; }
th, td { border: 0.6pt solid #cbd5e0; padding: 3pt 5pt; text-align: left; vertical-align: top; }
th { background: #edf2f7; color: #1a365d; }
thead { display: table-header-group; }
tr { break-inside: avoid; }
hr { border: 0; border-top: 0.6pt dashed #cbd5e0; margin: 8pt 0; }
.notes { border: 0.6pt dashed #b0b8c4; border-radius: 3pt; height: 40pt; margin: 6pt 0 10pt; padding: 3pt 6pt;
         background: repeating-linear-gradient(#fff 0, #fff 12pt, #eef1f5 12pt, #eef1f5 12.6pt); }
.notes span { font-size: 8pt; color: #a0aec0; }
.toc { margin-top: 12pt; }
.toc li { font-size: 9.5pt; }
"""


def page_html(inner, doc_title):
    return f"""<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">
<title>{esc(doc_title)}</title><style>{CSS}</style></head><body>{inner}</body></html>"""


def cover(book_title, sub, extra=''):
    today = datetime.date.today().strftime('%Y 年 %m 月 %d 日')
    return f"""<div class="cover">
      <h1 class="cover-title">软件项目管理</h1>
      <div class="cover-sub">课堂教学讲稿 · {esc(book_title)}</div>
      <div class="cover-sub" style="font-size:10.5pt;color:#718096;">{esc(sub)}</div>
      <div class="cover-meta">
        课程代码 154431006 ｜ 32 学时 / 2 学分 ｜ 广东金融学院 计算机学院<br>
        配套课件：http://111.231.0.226:1234/　｜　备课台：http://192.168.31.76:8910/备课台/index.html<br>
        打印时间：{today}{('<br>' + extra) if extra else ''}
      </div></div>"""


def build_one(nos, out_name, book_title, sub, with_notes=False, cover_extra=''):
    inner = [cover(book_title, sub, cover_extra)]
    for k, no in enumerate(nos):
        md = open(os.path.join(HERE, CH[no]), encoding='utf-8').read()
        html = convert(md, with_notes=with_notes)
        html = html.replace('<h1 class="chap">', '<h1 class="chap first">', 1) if k == 0 else html
        inner.append(html)
    path = os.path.join(OUT, out_name)
    open(path, 'w', encoding='utf-8').write(page_html('\n'.join(inner), book_title))
    print('✓', out_name)
    return path


def main():
    build_one([1], 'ch1.html', '第 1 章 软件项目管理概述', SUBTITLE[1])
    build_one([2], 'ch2.html', '第 2 章 软件项目启动', SUBTITLE[2])
    build_one([1, 2], 'both.html', '第 1–2 章（合订）', '第 1 周 + 第 2 周 ｜ 共 80 页课件 · 51,689 字口播')
    build_one([1, 2], 'both-annot.html', '第 1–2 章（备课批注版）', '第 1 周 + 第 2 周 ｜ 每节后留批注横线',
              with_notes=True, cover_extra='本册每节之后留有「我的批注」横线区，供手写备课要点。')
    print('HTML 生成完成 →', OUT)


if __name__ == '__main__':
    main()
