#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""把 35 页正文 .pptx 完整转写为 Markdown（逐字取自文件，不手抄）。

- 顺序：按形状 (top, left) 阅读顺序
- 卡片/要点/表格/待补占位/演讲者备注 分别成块
- 生成后做完整性自检：页数、文本字符数、备注数、待补数
"""
from pptx import Presentation
from pptx.util import Emu
import io, os, re, datetime

HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, '宝锐诊断原料_客户宣讲正文_35页_v1.pptx')
OUT = os.path.join(HERE, '宝锐诊断原料_客户宣讲正文_35页.md')
EMU = 914400

prs = Presentation(SRC)
SLOGAN = '守护生命健康，缔造美好生活'
COMPANY_CLEAN = '珠海宝锐生物科技有限公司'


def paras(sh):
    out = []
    for p in sh.text_frame.paragraphs:
        t = ''.join(r.text for r in p.runs).strip()
        if t:
            out.append((t, bool(p.level)))
    return out


def fontsize(sh):
    sizes = [r.font.size.pt for p in sh.text_frame.paragraphs for r in p.runs if r.font.size]
    return max(sizes) if sizes else 11.0


def is_bold(sh):
    for p in sh.text_frame.paragraphs:
        for r in p.runs:
            if r.text.strip():
                return bool(r.font.bold)
    return False


def table_md(tbl):
    rows = []
    for r in tbl.rows:
        cells = []
        for c in r.cells:
            cells.append(' '.join(c.text.split()).replace('|', '\\|'))
        rows.append(cells)
    if not rows:
        return []
    md = ['| ' + ' | '.join(rows[0]) + ' |', '|' + '|'.join([' --- '] * len(rows[0])) + '|']
    for r in rows[1:]:
        md.append('| ' + ' | '.join(r) + ' |')
    return md


lines = []
lines.append('# 宝锐诊断原料 · 客户宣讲正文（35 页）')
lines.append('')
lines.append('> 本文件由 `宝锐诊断原料_客户宣讲正文_35页_v1.pptx` 完整转写，文字与源文件逐字一致。')
lines.append('> 适用范围：面向诊断原料客户（IVD 厂家研发 / 采购 / 老板）现场宣讲；口径为「可露货号 + 结论级数据，不露原始曲线与客户名」。')
lines.append('')
lines.append('- **源文件**：宝锐诊断原料_客户宣讲正文_35页_v1.pptx')
lines.append('- **页数**：35 页（5 个章节）｜ **建议时长**：30 分钟（Q&A 另留 5 分钟）')
lines.append('- **品牌要素**：白顶栏 Logo ＋ slogan「守护生命健康，缔造美好生活」；结尾页含公众号/小红书/视频号三码')
lines.append('- **转写时间**：' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M'))
lines.append('')

stat_shapes = 0
stat_notes = 0
stat_todo = 0
stat_tables = 0
chars = 0

for idx, slide in enumerate(prs.slides, 1):
    shapes = [sh for sh in slide.shapes if (sh.has_text_frame and sh.text_frame.text.strip()) or sh.has_table]
    shapes.sort(key=lambda sh: (round(sh.top / EMU, 1), round(sh.left / EMU, 1)))

    # 主标题 = 字号最大且靠上的文字（排除 slogan/公司名/纯编号）
    def is_chrome(t):
        return (t == SLOGAN or t == COMPANY_CLEAN or t.startswith(COMPANY_CLEAN)
                or t.startswith(SLOGAN) or re.match(r'^\d+\s*/\s*35$', t))
    def is_no(t):
        return bool(re.fullmatch(r'0?\d{1,2}', t)) and len(t) <= 2
    heads = sorted([sh for sh in shapes if sh.has_text_frame and fontsize(sh) >= 20],
                   key=lambda sh: -fontsize(sh))
    title, chapter_no = None, None
    for h in heads:
        t = ' '.join(h.text_frame.text.split())
        if not t or is_chrome(t):
            continue
        if chapter_no is None and is_no(t):
            chapter_no = t.zfill(2)
            continue
        if title is None:
            title = t
    if chapter_no and title:
        title = chapter_no + ' · ' + title
    skip = set()
    if title:
        skip.add(title.split(' · ', 1)[-1] if chapter_no else title)
    if chapter_no:
        skip.add(chapter_no.lstrip('0'))
        skip.add(chapter_no)
    body = [sh for sh in shapes
            if not (sh.has_text_frame and ' '.join(sh.text_frame.text.split()) in skip)]

    lines.append('---')
    lines.append('')
    lines.append('## P%d. %s' % (idx, title or '（无标题）'))
    lines.append('')

    seen = set()
    for sh in body:
        if sh.has_table:
            stat_tables += 1
            lines.extend(table_md(sh.table))
            lines.append('')
            continue
        txt = ' '.join(sh.text_frame.text.split())
        if not txt or txt in seen:
            continue
        seen.add(txt)
        if txt == SLOGAN or (SLOGAN in txt and COMPANY_CLEAN in txt) or txt == COMPANY_CLEAN:
            continue
        txt_nospace = txt.replace(' ', '')
        if txt_nospace in [s.replace(' ', '') for s in skip] or re.fullmatch(r'0?\d{1,2}', txt):
            continue
        if txt.startswith('⚠️ 待补'):
            stat_todo += 1
            lines.append('> ⚠️ **待补**：' + txt.replace('⚠️ 待补：', '').strip())
            lines.append('')
            continue
        if re.match(r'^\d+\s*/\s*35$', txt):
            continue
        ps = paras(sh)
        fs, bold = fontsize(sh), is_bold(sh)
        if len(ps) == 1 and (bold or fs >= 13):
            lines.append('**%s**' % ps[0][0])
            lines.append('')
        else:
            if len(ps) > 1 and (bold or fs >= 13):
                lines.append('**%s**' % ps[0][0])
                ps = ps[1:]
            for t, lvl in ps:
                lines.append(('  ' if lvl else '') + '- ' + t)
            lines.append('')
        stat_shapes += 1
        chars += len(txt)

    if slide.has_notes_slide:
        nt = slide.notes_slide.notes_text_frame.text.strip()
        if nt:
            stat_notes += 1
            nt = re.sub(r'\n*【页码】\d+ / 35\s*$', '', nt).strip()
            lines.append('> 🎤 **讲法**：' + ' '.join(nt.split()))
            lines.append('')

io.open(OUT, 'w', encoding='utf-8').write('\n'.join(lines).rstrip() + '\n')

print('  ✅ %s' % os.path.basename(OUT))
print('     页数: %d ｜ 文本块: %d ｜ 表格: %d ｜ 待补占位: %d ｜ 讲法备注: %d ｜ 正文字符: %d'
      % (len(prs.slides), stat_shapes, stat_tables, stat_todo, stat_notes, chars))
print('     文件大小: %.1f KB' % (os.path.getsize(OUT) / 1024))
