# -*- coding: utf-8 -*-
"""
生成培训课件 PPT（16:9，不少于10页）。

从头搭建版式，不依赖任何现成模板，保证每次生成都稳定可控。

用法:
    python build_pptx.py --config content.json --out "输出文件.pptx"

content.json 结构见 references/字段说明.md
"""
import argparse
import json
import sys
from pathlib import Path

from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.util import Inches, Pt

# ---- 设计常量：改内容不要改这些 ----
NAVY = RGBColor(0x1F, 0x38, 0x64)      # 主色 深蓝
ORANGE = RGBColor(0xED, 0x7D, 0x31)    # 强调色 橙
GREY = RGBColor(0x59, 0x59, 0x59)      # 正文灰
LIGHT = RGBColor(0xF2, 0xF5, 0xFA)     # 浅底
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
FONT = "微软雅黑"

W, H = Inches(13.333), Inches(7.5)
MIN_SLIDES = 10


def textbox(slide, l, t, w, h, text, size, color, bold=False,
            align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, spacing=1.0):
    tb = slide.shapes.add_textbox(l, t, w, h)
    tf = tb.text_frame
    tf.word_wrap = True
    tf.vertical_anchor = anchor
    lines = text.split("\n")
    for i, line in enumerate(lines):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = align
        p.line_spacing = spacing
        r = p.add_run()
        r.text = line
        r.font.size = Pt(size)
        r.font.color.rgb = color
        r.font.bold = bold
        r.font.name = FONT
    return tb


def rect(slide, l, t, w, h, color):
    from pptx.enum.shapes import MSO_SHAPE
    sh = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, l, t, w, h)
    sh.fill.solid()
    sh.fill.fore_color.rgb = color
    sh.line.fill.background()
    sh.shadow.inherit = False
    return sh


def blank(prs):
    return prs.slides.add_slide(prs.slide_masters[0].slide_layouts[6])


# ---------------- 各类页面 ----------------

def cover(prs, title, subtitle, unit, date):
    s = blank(prs)
    rect(s, 0, 0, W, H, NAVY)
    rect(s, Inches(1.0), Inches(3.05), Inches(1.4), Pt(6), ORANGE)
    textbox(s, Inches(1.0), Inches(1.9), Inches(11.3), Inches(1.1),
            title, 44, WHITE, bold=True)
    if subtitle:
        textbox(s, Inches(1.0), Inches(3.45), Inches(11.3), Inches(0.7),
                subtitle, 20, RGBColor(0xC7, 0xD3, 0xE8))
    textbox(s, Inches(1.0), Inches(5.9), Inches(11.3), Inches(0.9),
            f"{unit}\n{date}", 14, RGBColor(0xA8, 0xBA, 0xD8), spacing=1.4)


def toc(prs, chapters):
    s = blank(prs)
    textbox(s, Inches(0.9), Inches(0.7), Inches(6), Inches(0.7),
            "目  录", 32, NAVY, bold=True)
    rect(s, Inches(0.9), Inches(1.5), Inches(0.9), Pt(4), ORANGE)
    top = Inches(2.2)
    for i, ch in enumerate(chapters):
        y = top + Inches(0.85) * i
        rect(s, Inches(0.9), y, Inches(0.62), Inches(0.62), NAVY)
        textbox(s, Inches(0.9), y + Inches(0.09), Inches(0.62), Inches(0.45),
                f"{i+1:02d}", 16, WHITE, bold=True, align=PP_ALIGN.CENTER)
        textbox(s, Inches(1.75), y + Inches(0.08), Inches(10.5), Inches(0.5),
                ch, 20, GREY)


def chapter(prs, idx, title):
    s = blank(prs)
    rect(s, 0, 0, W, H, LIGHT)
    rect(s, 0, Inches(2.9), Inches(0.35), Inches(1.7), ORANGE)
    textbox(s, Inches(1.1), Inches(2.6), Inches(3), Inches(1.3),
            f"{idx:02d}", 76, RGBColor(0xD5, 0xDD, 0xEC), bold=True)
    textbox(s, Inches(1.15), Inches(3.85), Inches(10.5), Inches(0.9),
            title, 34, NAVY, bold=True)


def content(prs, title, points):
    """points: [(要点标题, 说明), ...]  最多 4 个"""
    s = blank(prs)
    rect(s, 0, 0, W, Inches(1.25), NAVY)
    textbox(s, Inches(0.85), Inches(0.32), Inches(11.6), Inches(0.6),
            title, 28, WHITE, bold=True)

    n = min(len(points), 4)
    top = Inches(1.85)
    gap = Inches(0.22)
    bh = (Inches(5.0) - gap * (n - 1)) / n if n else Inches(5.0)

    for i, (ptitle, desc) in enumerate(points[:4]):
        y = top + (bh + gap) * i
        rect(s, Inches(0.85), y, Inches(11.6), bh, LIGHT)
        rect(s, Inches(0.85), y, Pt(5), bh, ORANGE)
        textbox(s, Inches(1.15), y + Inches(0.13), Inches(11.0), Inches(0.42),
                ptitle, 18, NAVY, bold=True)
        textbox(s, Inches(1.15), y + Inches(0.6), Inches(11.0), bh - Inches(0.7),
                desc, 14, GREY, spacing=1.3)


def recap(prs, items):
    s = blank(prs)
    rect(s, 0, 0, W, Inches(1.25), NAVY)
    textbox(s, Inches(0.85), Inches(0.32), Inches(11.6), Inches(0.6),
            "核心要点回顾", 28, WHITE, bold=True)
    top = Inches(1.9)
    for i, it in enumerate(items[:6]):
        y = top + Inches(0.82) * i
        rect(s, Inches(0.95), y + Inches(0.1), Inches(0.34), Inches(0.34), ORANGE)
        textbox(s, Inches(0.95), y + Inches(0.13), Inches(0.34), Inches(0.3),
                str(i + 1), 13, WHITE, bold=True, align=PP_ALIGN.CENTER)
        textbox(s, Inches(1.5), y + Inches(0.06), Inches(10.9), Inches(0.7),
                it, 16, GREY, spacing=1.25)


def ending(prs, slogan, unit):
    s = blank(prs)
    rect(s, 0, 0, W, H, NAVY)
    textbox(s, Inches(1.0), Inches(3.0), Inches(11.3), Inches(1.0),
            slogan, 36, WHITE, bold=True, align=PP_ALIGN.CENTER)
    rect(s, Inches(6.17), Inches(4.2), Inches(1.0), Pt(5), ORANGE)
    textbox(s, Inches(1.0), Inches(4.7), Inches(11.3), Inches(0.6),
            unit, 16, RGBColor(0xA8, 0xBA, 0xD8), align=PP_ALIGN.CENTER)


# ---------------- 主流程 ----------------

def build(cfg, out_path):
    prs = Presentation()
    prs.slide_width, prs.slide_height = W, H

    title = cfg["培训主题"]
    unit = cfg.get("单位", "××工程局  ××光伏项目部")
    date = cfg.get("培训时间", "")
    chapters = cfg["章节"]

    cover(prs, title, cfg.get("副标题", "安全教育培训"), unit, date)
    toc(prs, [c["标题"] for c in chapters])

    for i, ch in enumerate(chapters, 1):
        chapter(prs, i, ch["标题"])
        for pt in ch["要点页"]:
            content(prs, pt["标题"], [(b["标题"], b["说明"]) for b in pt["要点"]])

    recap(prs, cfg.get("核心要点", [c["标题"] for c in chapters]))
    ending(prs, cfg.get("结束语", "安全无小事  责任重于山"), unit)

    Path(out_path).parent.mkdir(parents=True, exist_ok=True)
    prs.save(out_path)
    return len(prs.slides._sldIdLst)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--config", required=True)
    ap.add_argument("--out", required=True)
    args = ap.parse_args()

    cfg = json.loads(Path(args.config).read_text(encoding="utf-8"))
    n = build(cfg, args.out)

    print(f"  {Path(args.out).name}   共 {n} 页")
    if n < MIN_SLIDES:
        print(f"  ⚠ 只有 {n} 页，少于要求的 {MIN_SLIDES} 页——增加章节或每章的要点页再跑一次")
        return 1
    return 0


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