#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""通用 pptx → Markdown 转写器（逐字取自文件）。

用法: python3 to_md_generic.py <input.pptx> <output.md> [标题]
- 阅读顺序：按 (top, left) 排序
- 每页取最大字号文本作标题（排除 slogan / 公司名 / 纯编号 / 页码）
- 表格转 md 表格；演讲者备注转引用块
"""
from pptx import Presentation
import io, os, re, sys, datetime

SRC = sys.argv[1]
OUT = sys.argv[2]
DOC_TITLE = sys.argv[3] if len(sys.argv) > 3 else os.path.splitext(os.path.basename(SRC))[0]
EMU = 914400
prs = Presentation(SRC)
TOTAL = len(prs.slides)

SLOGANS = ('守护生命健康·缔造美好生活', '守护生命健康 缔造美好生活', '守护生命健康，缔造美好生活')
COMPANIES = ('珠海宝锐生物科技有限公司',)


def chrome(t):
    """页脚装饰串判定：必须以"整串就是它"或"短串开头"为准，
    否则正文段落（如以公司名开头的简介）会被误杀。"""
    if t in SLOGANS or t in COMPANIES:
        return True
    if '守护生命健康' in t and '缔造美好生活' in t and len(t) <= 40:
        return True
    for pre in COMPANIES + SLOGANS:
        if t.startswith(pre) and len(t) <= len(pre) + 25:
            return True
    if re.fullmatch(r'\d+\s*/\s*\d+', t):
        return True
    return False


def numeric(t):
    """纯数量标签（如 '59个'）不能当标题"""
    return bool(re.fullmatch(r'[\d,\.]+\s*(个|项|人|万|%|分|套|台|余)?', t))


def is_no(t):
    return bool(re.fullmatch(r'(0?\d{1,2}|0\d)', t)) and len(t) <= 2


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, p.level))
    return out


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


def 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:
        rows.append([' '.join(c.text.split()).replace('|', '\\|') for c in r.cells])
    if not rows or not rows[0]:
        return []
    md = ['| ' + ' | '.join(rows[0]) + ' |', '|' + '|'.join([' --- '] * len(rows[0])) + '|']
    for r in rows[1:]:
        md.append('| ' + ' | '.join(r) + ' |')
    return md


L = ['# ' + DOC_TITLE, '',
     '> 由 `%s` 完整转写（文字与表格逐字取自源文件，未做改写）。' % os.path.basename(SRC), '',
     '- **页数**：%d 页' % TOTAL,
     '- **转写时间**：' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M'), '']

stat = dict(blocks=0, tables=0, notes=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)))

    heads = sorted([sh for sh in shapes if sh.has_text_frame and fsize(sh) >= 18], key=lambda sh: -fsize(sh))
    title, no = None, None
    for h in heads:
        t = ' '.join(h.text_frame.text.split())
        if not t or chrome(t) or numeric(t):
            continue
        if no is None and is_no(t):
            no = t.zfill(2)
            continue
        title = t
        break
    if title is None:
        for sh in shapes:
            if sh.has_text_frame:
                t = ' '.join(sh.text_frame.text.split())
                if t and not chrome(t) and not is_no(t):
                    title = t
                    break
    skip = set()
    if title:
        skip.add(title)
        skip.add(title.replace(' ', ''))
    if no:
        skip.update({no, no.lstrip('0')})

    L += ['---', '', '## P%d. %s' % (idx, (no + ' · ' + title) if (no and title) else (title or '（无标题）')), '']

    seen = set()
    for sh in shapes:
        if sh.has_table:
            stat['tables'] += 1
            L += table_md(sh.table) + ['']
            continue
        if not sh.has_text_frame:
            continue
        raw = sh.text_frame.text
        flat = ' '.join(raw.split())
        if not flat or chrome(flat) or flat in seen:
            continue
        if flat in skip or flat.replace(' ', '') in skip or is_no(flat):
            continue
        seen.add(flat)
        ps = paras(sh)
        fs, bd = fsize(sh), bold(sh)
        if len(ps) == 1 and (bd or fs >= 13):
            L += ['**%s**' % ps[0][0], '']
        else:
            if len(ps) > 1 and (bd or fs >= 13):
                L += ['**%s**' % ps[0][0]]
                ps = ps[1:]
            for t, lvl in ps:
                L += [('  ' if lvl else '') + '- ' + t]
            L += ['']
        stat['blocks'] += 1
        stat['chars'] += len(flat)

    if slide.has_notes_slide:
        nt = ' '.join(slide.notes_slide.notes_text_frame.text.split())
        if nt:
            stat['notes'] += 1
            L += ['> 🎤 **备注**：' + nt, '']

io.open(OUT, 'w', encoding='utf-8').write('\n'.join(L).rstrip() + '\n')
print('  ✅ %s' % os.path.basename(OUT))
print('     页数 %d ｜ 文本块 %d ｜ 表格 %d ｜ 备注 %d ｜ 正文字符 %d ｜ %.1f KB'
      % (TOTAL, stat['blocks'], stat['tables'], stat['notes'], stat['chars'], os.path.getsize(OUT) / 1024))
