# -*- coding: utf-8 -*-
"""把扩写分片拼装为最终两章讲稿，并做完整性/密度校验。
用法：python3 _assemble_v2.py            # 校验 + 生成
      python3 _assemble_v2.py --check    # 只校验不写文件
拼装顺序：head + p1a + p1b + p2a + p2b + tail
"""
import os, re, sys

HERE = os.path.dirname(os.path.abspath(__file__))
P = os.path.join(HERE, '_parts')
V1 = os.path.join(HERE, '_v1要点版')

CHAPTERS = [
    dict(no=1, pages=52, final='第1章-软件项目管理概述-讲稿.md',
         v1='第1章-软件项目管理概述-讲稿.md',
         parts=['ch1-head.md', 'ch1-p1a.md', 'ch1-p1b.md', 'ch1-p2a.md', 'ch1-p2b.md', 'ch1-tail.md'],
         min_spoken=22000),
    dict(no=2, pages=28, final='第2章-软件项目启动-讲稿.md',
         v1='第2章-软件项目启动-讲稿.md',
         parts=['ch2-head.md', 'ch2-p1a.md', 'ch2-p1b.md', 'ch2-p2a.md', 'ch2-p2b.md', 'ch2-tail.md'],
         min_spoken=16000),
]

ANCHOR = re.compile(r'^###\s+【(\d+)\.(\d+)(?:\s*[–\-—]\s*\d+\.(\d+))?】\s*(.*)$', re.M)


def han(s):
    return len(re.sub(r'[^\u4e00-\u9fff]', '', s))


def split_sections(md):
    """→ [(章, 起页, 止页|None, 标题, 正文)]"""
    ms = list(ANCHOR.finditer(md))
    out = []
    for i, m in enumerate(ms):
        end = ms[i + 1].start() if i + 1 < len(ms) else len(md)
        out.append((m.group(1), m.group(2), m.group(3), m.group(4).strip(), md[m.start():end]))
    return out


def spoken_of(body):
    return sum(han(l) for l in body.split('\n') if l.strip().startswith('>'))


def main():
    check_only = '--check' in sys.argv
    all_ok, built = True, []
    for C in CHAPTERS:
        missing = [p for p in C['parts'] if not os.path.isfile(os.path.join(P, p))]
        if missing:
            print(f"❌ 第{C['no']}章缺少分片: {missing}")
            all_ok = False
            continue
        md = '\n'.join(
            '\n'.join(l for l in open(os.path.join(P, p), encoding='utf-8').read().split('\n')
                      if not l.lstrip().startswith('<!--')).rstrip() + '\n'
            for p in C['parts'])
        secs = split_sections(md)
        covered = []
        for ch, p1, p2, _t, _b in secs:
            covered.append(f'{ch}.{p1}')
            if p2:
                for k in range(int(p1) + 1, int(p2) + 1):
                    covered.append(f'{ch}.{k}')
        expect = [f"{C['no']}.{i}" for i in range(1, C['pages'] + 1)]
        missed = [x for x in expect if x not in covered]
        dup = sorted({x for x in covered if covered.count(x) > 1})
        total = sum(spoken_of(b) for *_x, b in secs)
        thin = [(f'{c}.{p1}', spoken_of(b)) for c, p1, _p2, _t, b in secs if spoken_of(b) < 200]
        v1p = os.path.join(V1, C['v1'])
        v1n = sum(spoken_of(b) for *_x, b in split_sections(open(v1p, encoding='utf-8').read())) \
            if os.path.isfile(v1p) else 0
        ok = (not missed) and (not dup) and total >= C['min_spoken']
        all_ok &= ok
        print(f"{'✅' if ok else '❌'} 第{C['no']}章：锚点 {len(secs)} 个 ｜ 覆盖 {len(set(covered))}/{C['pages']} 页 ｜ "
              f"口播 {total} 字（v1 {v1n} 字，×{total / max(1, v1n):.1f}）｜ 达标线 {C['min_spoken']} 字")
        if missed:
            print('   ❌ 缺页:', ' '.join(missed))
        if dup:
            print('   ❌ 页号重复:', ' '.join(dup))
        if thin:
            show = ', '.join(f'{k}({v})' for k, v in thin[:12])
            print(f'   ⚠ 口播偏少(<200字) {len(thin)} 页: {show}' + (' …' if len(thin) > 12 else ''))
        built.append((C, md, secs, total))

    if not all_ok:
        print('\n⛔ 校验未通过，未写文件。请修正分片后重跑。')
        return 1
    if check_only:
        print('\n✅ 校验通过（--check：未写文件）')
        return 0
    for C, md, secs, total in built:
        with open(os.path.join(HERE, C['final']), 'w', encoding='utf-8') as f:
            f.write(md)
        print(f"📄 已生成 {C['final']}：{len(md.encode('utf-8')) / 1024:.0f} KB ｜ {len(secs)} 页 ｜ 口播 {total} 字")
    print('\n✅ 两章 v2 拼装完成 → 下一步执行：python3 _build_prep.py')
    return 0


if __name__ == '__main__':
    sys.exit(main())
