#!/usr/bin/env python3
"""Generate three-version PC/mobile concise manuals from the retained CPT Word template."""

from __future__ import annotations

import subprocess
import re
import zipfile
from pathlib import Path

from docx import Document
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Inches, Pt, RGBColor


OUTPUT_ROOT = Path("/Users/jack/Desktop/智慧食堂三版本演示账号与精简手册_20260902")
TEMPLATE = Path(
    "/Users/jack/.codex/skills/kangbite-word-template/assets/"
    "kangbite-digital-sports-word-template.docx"
)
PREPARE = Path(
    "/Users/jack/.codex/skills/kangbite-word-template/scripts/prepare_working_copy.py"
)
SCREENSHOT_ROOT = OUTPUT_ROOT / "04-内部验收与来源" / "截图"

ORANGE = "F58220"
DARK = "25364A"
LIGHT_ORANGE = "FFF1E6"
GRAY = "F5F7FA"
WHITE = "FFFFFF"
CJK_FONT = "Arial Unicode MS"


VERSIONS = [
    {
        "folder": "01-智能结算基础版",
        "name": "智能结算基础版",
        "pc_account": "demo_cpt_basic_202609",
        "mobile_account": "19900090001",
        "pc_screenshot": "pc-基础版-首页.png",
        "mobile_screenshot": "mobile-基础版-首次登录.png",
        "positioning": "面向单食堂的最小结算闭环，突出人员、卡、补贴、消费订单和经营对账。",
        "pc_flow": "人员查询 → 卡片管理 → 补贴发放 → 刷卡消费 → 订单查询 → 统计对账",
        "pc_modules": [
            ("人员管理", "机构人员查看、卡片管理、卡务操作记录"),
            ("账户管理", "补贴发放、补贴规则、账户流水查询"),
            ("订单管理", "消费订单查询"),
            ("统计报表", "人员、部门、设备、餐厅经营、档口营收、充值与补贴统计"),
        ],
        "pc_steps": [
            ("登录并确认范围", "登录后核对右上角账号为“演示-智能结算基础版”，餐厅范围为演示餐厅。"),
            ("查人员和卡", "在人员管理中查看测试人员；在卡务管理中查看卡片状态和操作记录。"),
            ("看补贴与流水", "在账户管理中查看补贴记录、发放规则及账户流水。演示时不提交真实金额。"),
            ("查消费订单", "在订单管理中按日期、人员或设备筛选消费订单，核对金额与状态。"),
            ("完成经营对账", "在统计报表中查看餐厅、档口、设备、充值和补贴汇总。"),
        ],
        "excluded": "不包含退款订单、消费规则、账单管理、系统管理、营养调查、配餐、膳食监控。",
        "mobile_included": False,
        "mobile_positioning": "基础版默认不交付小程序端；本账号仅用于内部验证移动端身份链路。",
        "mobile_flow": "默认不含小程序 → 如项目增购，再启用用户端账号、支付与订单能力",
        "mobile_modules": [
            ("标准交付", "不包含用户端/小程序"),
            ("内部测试账号", "仅用于验证手机号账号、首次资料页和系统连通性"),
        ],
        "mobile_steps": [
            ("确认售卖边界", "销售报价和方案中默认写明“不含小程序端”。"),
            ("需要移动端时升级", "客户提出余额、付款码、订单或营养报告需求时，升级到营养结算标准版或营养健康专业版。"),
            ("内部联调", "如需验证账号链路，可使用内部测试账号登录；首次进入会要求完成人脸资料。"),
        ],
    },
    {
        "folder": "02-营养结算标准版",
        "name": "营养结算标准版",
        "pc_account": "demo_cpt_standard_202609",
        "mobile_account": "19900090002",
        "pc_screenshot": "pc-标准版-首页.png",
        "mobile_screenshot": "mobile-标准版-首次登录.png",
        "positioning": "在结算闭环上增加菜谱、菜品、电子菜牌、营养统计和个体膳食监控。",
        "pc_flow": "人员与账户 → 菜谱菜品 → 消费结算 → 营养统计 → 个体膳食监控",
        "pc_modules": [
            ("人员管理", "机构人员查看、卡片管理、卡务操作记录"),
            ("配餐管理", "合理配餐结果查看"),
            ("账户与订单", "补贴、流水、消费订单"),
            ("餐厅管理", "菜谱编辑、菜品查看、称重记录、电子菜牌及配置"),
            ("统计报表", "经营报表、菜品销量、菜品营养统计"),
            ("膳食监控", "个体能量、营养结构、维生素、矿物质、餐次及膳食结构分析"),
        ],
        "pc_steps": [
            ("登录并确认范围", "确认账号为“演示-营养结算标准版”，只使用测试部门和演示餐厅。"),
            ("维护菜单内容", "查看菜品，按演示脚本进入菜谱和电子菜牌；非必要不保存改动。"),
            ("查看结算记录", "通过账户流水和消费订单核对人员、金额、设备与时间。"),
            ("查看营养结果", "从菜品营养统计和个体膳食监控展示能量与营养结构。"),
            ("输出经营报表", "通过餐厅、档口、设备、充值与补贴报表完成经营汇总。"),
        ],
        "excluded": "不包含退款订单、消费规则、账单管理、系统管理、团体膳食监控、营养调查和营养资讯。",
        "mobile_included": True,
        "mobile_positioning": "面向就餐者提供账户、消费和个人营养结果的轻量用户端。",
        "mobile_flow": "登录 → 查看余额/消费码 → 完成消费 → 查订单 → 看营养日报与阶段报告",
        "mobile_modules": [
            ("账号与账户", "手机号登录、余额与账户明细"),
            ("消费", "动态消费二维码"),
            ("订单", "消费记录与订单详情"),
            ("个人营养", "单餐分析、营养日报、阶段报告"),
            ("我的", "基本信息、密码和退出登录"),
        ],
        "mobile_steps": [
            ("登录", "使用手机号账号登录。首次进入需补齐资料并完成人脸采集；此动作只针对测试人员。"),
            ("展示账户与消费", "在“我的”查看余额，在就餐时打开动态消费二维码。"),
            ("查询订单", "在订单页查看消费记录、金额和订单详情。"),
            ("查看营养结果", "在首页进入单餐分析、营养日报和阶段报告。"),
            ("控制演示范围", "演示环境可能显示专业版入口，标准版演示只按本手册路径操作。"),
        ],
    },
    {
        "folder": "03-营养健康专业版",
        "name": "营养健康专业版",
        "pc_account": "demo_cpt_pro_202609",
        "mobile_account": "19900090003",
        "pc_screenshot": "pc-专业版-首页.png",
        "mobile_screenshot": "mobile-专业版-首次登录.png",
        "positioning": "覆盖调查、配餐、就餐采集、个体/团体监控和持续营养干预的专业闭环。",
        "pc_flow": "营养调查 → 合理配餐 → 就餐采集 → 个体/团体监控 → 体重与营养干预",
        "pc_modules": [
            ("营养调查", "膳食调查查看"),
            ("配餐管理", "合理配餐结果查看"),
            ("人员、账户与订单", "人员卡务、补贴流水、消费订单"),
            ("餐厅管理", "菜谱、菜品、食物成分、称重记录与电子菜牌"),
            ("统计报表", "经营、营养、体重管理和打卡排行"),
            ("膳食监控", "个体与团体的能量、营养素、餐次、膳食结构等分析"),
            ("营养资讯", "合理膳食、专项营养、疲劳恢复、体重管理内容"),
        ],
        "pc_steps": [
            ("登录并确认范围", "确认账号为“演示-营养健康专业版”，只使用测试部门与演示餐厅。"),
            ("了解人群", "查看膳食调查和人员档案，明确营养管理对象。"),
            ("制定与发布餐单", "查看合理配餐、菜谱、菜品、食物成分和电子菜牌。"),
            ("采集就餐结果", "通过称重记录、账户流水和消费订单形成真实摄入基础。"),
            ("分析与干预", "展示个体/团体膳食监控、体重管理统计、打卡排行和营养资讯。"),
        ],
        "excluded": "不包含退款订单、消费规则、账单管理和系统管理；不把演示环境写成医疗诊断系统。",
        "mobile_included": True,
        "mobile_positioning": "面向持续营养健康管理，提供个人摄入、运动、体重、报告与 AI 营养问答。",
        "mobile_flow": "登录建档 → 饮食采集 → 营养日报/阶段报告 → 运动与体重 → AI营养问答",
        "mobile_modules": [
            ("首页", "日历、能量仪表盘、饮食与单餐分析"),
            ("营养报告", "营养日报和阶段报告"),
            ("运动与体重", "运动记录、步数消耗、体重趋势"),
            ("账户与消费", "余额、消费码、订单"),
            ("AI 营养问答", "营养与运动问答、图片资料辅助分析"),
            ("我的", "基本信息、密码、人脸与退出登录"),
        ],
        "mobile_steps": [
            ("登录建档", "使用手机号登录，首次补齐资料并完成测试人脸采集。"),
            ("查看摄入", "在首页查看当日能量、三大营养素和单餐分析。"),
            ("查看报告", "进入营养日报和阶段报告，了解结构变化。"),
            ("记录运动与体重", "查看运动消耗和体重趋势；演示数据只用于功能说明。"),
            ("使用 AI 问答", "围绕营养、运动和饮食提出问题，不用于疾病诊断和治疗决策。"),
        ],
    },
]


def set_east_asia_font(run, font_name: str = CJK_FONT) -> None:
    run._element.get_or_add_rPr().get_or_add_rFonts().set(qn("w:eastAsia"), font_name)


def set_cell_shading(cell, fill: str) -> None:
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = tc_pr.find(qn("w:shd"))
    if shd is None:
        shd = OxmlElement("w:shd")
        tc_pr.append(shd)
    shd.set(qn("w:val"), "clear")
    shd.set(qn("w:fill"), fill)


def repair_document_xml(path: Path) -> None:
    """Remove stale mc:Ignorable names that python-docx no longer declares."""
    temp = path.with_suffix(".repair.docx")
    with zipfile.ZipFile(path, "r") as source, zipfile.ZipFile(temp, "w", zipfile.ZIP_DEFLATED) as target:
        for item in source.infolist():
            data = source.read(item.filename)
            if item.filename == "word/document.xml":
                data = re.sub(rb'\s+[A-Za-z0-9_]+:Ignorable="[^"]*"', b"", data, count=1)
            if item.filename == "word/header1.xml":
                font_xml = (
                    b'<w:rFonts w:ascii="Arial Unicode MS" w:hAnsi="Arial Unicode MS" '
                    b'w:eastAsia="Arial Unicode MS"/>'
                )
                data = re.sub(rb'<w:rFonts\b[^>]*/>', font_xml, data)
                data = re.sub(rb'<w:rPr>(?!<w:rFonts)', b'<w:rPr>' + font_xml, data)
            target.writestr(item, data)
    temp.replace(path)


def set_cell_text(cell, text: str, *, bold: bool = False, color: str = DARK) -> None:
    cell.text = ""
    paragraph = cell.paragraphs[0]
    run = paragraph.add_run(text)
    run.bold = bold
    run.font.name = CJK_FONT
    set_east_asia_font(run)
    run.font.size = Pt(9.5)
    run.font.color.rgb = RGBColor.from_string(color)
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER


def add_title(doc: Document, title: str, subtitle: str) -> None:
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p.paragraph_format.space_before = Pt(12)
    p.paragraph_format.space_after = Pt(6)
    r = p.add_run(title)
    r.bold = True
    r.font.name = CJK_FONT
    set_east_asia_font(r)
    r.font.size = Pt(24)
    r.font.color.rgb = RGBColor.from_string(ORANGE)

    p2 = doc.add_paragraph()
    p2.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p2.paragraph_format.space_after = Pt(16)
    r2 = p2.add_run(subtitle)
    r2.font.name = CJK_FONT
    set_east_asia_font(r2)
    r2.font.size = Pt(11)
    r2.font.color.rgb = RGBColor.from_string(DARK)


def add_heading(doc: Document, text: str, level: int = 1) -> None:
    p = doc.add_paragraph(style=f"Heading {level}")
    p.paragraph_format.space_before = Pt(10 if level == 1 else 6)
    p.paragraph_format.space_after = Pt(5)
    run = p.add_run(text)
    run.bold = True
    run.font.name = CJK_FONT
    set_east_asia_font(run)
    run.font.size = Pt(15 if level == 1 else 12)
    run.font.color.rgb = RGBColor.from_string(ORANGE if level == 1 else DARK)


def add_body(doc: Document, text: str, *, bold: bool = False, color: str = DARK) -> None:
    p = doc.add_paragraph()
    p.paragraph_format.space_after = Pt(4)
    p.paragraph_format.line_spacing = 1.25
    run = p.add_run(text)
    run.bold = bold
    run.font.name = CJK_FONT
    set_east_asia_font(run)
    run.font.size = Pt(10.5)
    run.font.color.rgb = RGBColor.from_string(color)


def add_key_value_table(doc: Document, rows: list[tuple[str, str]]) -> None:
    table = doc.add_table(rows=0, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    for key, value in rows:
        cells = table.add_row().cells
        cells[0].width = Cm(3.2)
        cells[1].width = Cm(12.0)
        set_cell_shading(cells[0], LIGHT_ORANGE)
        set_cell_text(cells[0], key, bold=True, color=ORANGE)
        set_cell_text(cells[1], value)


def add_module_table(doc: Document, modules: list[tuple[str, str]]) -> None:
    table = doc.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    header = table.rows[0].cells
    for idx, label in enumerate(("一级模块", "本版本保留功能")):
        set_cell_shading(header[idx], ORANGE)
        set_cell_text(header[idx], label, bold=True, color=WHITE)
    for module, scope in modules:
        cells = table.add_row().cells
        set_cell_shading(cells[0], GRAY)
        set_cell_text(cells[0], module, bold=True)
        set_cell_text(cells[1], scope)


def add_steps(doc: Document, steps: list[tuple[str, str]]) -> None:
    for index, (title, detail) in enumerate(steps, start=1):
        p = doc.add_paragraph()
        p.paragraph_format.left_indent = Cm(0.2)
        p.paragraph_format.space_after = Pt(4)
        lead = p.add_run(f"{index:02d}  {title}：")
        lead.bold = True
        lead.font.color.rgb = RGBColor.from_string(ORANGE)
        lead.font.name = CJK_FONT
        set_east_asia_font(lead)
        lead.font.size = Pt(10.5)
        body = p.add_run(detail)
        body.font.name = CJK_FONT
        set_east_asia_font(body)
        body.font.size = Pt(10.5)
        body.font.color.rgb = RGBColor.from_string(DARK)


def add_image(doc: Document, path: Path, caption: str, width: float) -> None:
    if not path.exists():
        return
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p.add_run().add_picture(str(path), width=Inches(width))
    cap = doc.add_paragraph()
    cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
    cap.paragraph_format.space_after = Pt(6)
    r = cap.add_run(caption)
    r.italic = True
    r.font.size = Pt(8.5)
    r.font.color.rgb = RGBColor(110, 118, 129)
    set_east_asia_font(r)


def configure_document(doc: Document) -> None:
    section = doc.sections[0]
    section.top_margin = Cm(2.2)
    section.bottom_margin = Cm(2.0)
    section.left_margin = Cm(3.175)
    section.right_margin = Cm(3.175)
    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = CJK_FONT
    normal._element.get_or_add_rPr().get_or_add_rFonts().set(qn("w:eastAsia"), CJK_FONT)
    normal.font.size = Pt(10.5)


def build_pc_manual(version: dict) -> Path:
    folder = OUTPUT_ROOT / version["folder"]
    output = folder / f"{version['name']}_PC端精简操作手册.docx"
    starter = folder / ".pc-starter.docx"
    subprocess.run(["python3", str(PREPARE), str(TEMPLATE), str(starter)], check=True)
    doc = Document(starter)
    configure_document(doc)

    add_title(doc, f"{version['name']}｜PC端精简操作手册", "按演示账号真实权限裁剪 · 2026年9月")
    add_key_value_table(
        doc,
        [
            ("登录地址", "https://zhct.yyangpt.cn/dist/#/homeMain"),
            ("演示账号", version["pc_account"]),
            ("有效期", "2026-09-02 00:00:00 至 2026-10-01 23:59:59"),
            ("产品定位", version["positioning"]),
        ],
    )
    add_heading(doc, "1. 主流程")
    add_body(doc, version["pc_flow"], bold=True, color=ORANGE)
    add_heading(doc, "2. 账号可见模块")
    add_module_table(doc, version["pc_modules"])
    add_heading(doc, "3. 极简演示步骤")
    add_steps(doc, version["pc_steps"])
    add_heading(doc, "4. 明确不展示")
    add_body(doc, version["excluded"])
    add_heading(doc, "5. 演示安全边界")
    add_body(doc, "该账号仅用于内部销售演示，数据范围限定为测试部门和演示餐厅。除演示脚本明确要求外，不提交发卡、充值、补贴、退款、改菜谱等写操作。")
    add_image(doc, SCREENSHOT_ROOT / version["pc_screenshot"], "当前演示账号首页及可见一级菜单", 6.0)
    doc.save(output)
    repair_document_xml(output)
    starter.unlink(missing_ok=True)
    return output


def build_mobile_manual(version: dict) -> Path:
    folder = OUTPUT_ROOT / version["folder"]
    output = folder / f"{version['name']}_用户端小程序精简操作手册.docx"
    starter = folder / ".mobile-starter.docx"
    subprocess.run(["python3", str(PREPARE), str(TEMPLATE), str(starter)], check=True)
    doc = Document(starter)
    configure_document(doc)

    label = "用户端小程序精简操作手册" if version["mobile_included"] else "用户端小程序边界说明"
    add_title(doc, f"{version['name']}｜{label}", "基于2026-02-06用户端手册裁剪 · 2026年9月")
    add_key_value_table(
        doc,
        [
            ("演示入口", "https://zhct.yyangpt.cn/phone/#/login"),
            ("测试账号", version["mobile_account"]),
            ("有效期", "2026-09-02 00:00:00 至 2026-10-01 23:59:59"),
            ("版本边界", version["mobile_positioning"]),
        ],
    )
    add_heading(doc, "1. 主流程")
    add_body(doc, version["mobile_flow"], bold=True, color=ORANGE)
    add_heading(doc, "2. 本版本使用范围")
    add_module_table(doc, version["mobile_modules"])
    add_heading(doc, "3. 极简操作步骤")
    add_steps(doc, version["mobile_steps"])
    add_heading(doc, "4. 当前演示环境说明")
    add_body(doc, "移动端采用人员手机号身份，与PC后台管理员账号分开。首次登录会进入基础资料页；必须使用测试人员资料，禁止录入真实客户或个人敏感信息。")
    add_body(doc, "移动端当前为共用全量演示壳，标准版的售卖范围以本手册为准；正式项目应通过部署配置或功能开关收敛，不以演示环境多出的入口作为交付承诺。")
    add_image(doc, SCREENSHOT_ROOT / version["mobile_screenshot"], "首次登录后的资料确认页（测试数据）", 2.6)
    doc.save(output)
    repair_document_xml(output)
    starter.unlink(missing_ok=True)
    return output


def main() -> None:
    if not TEMPLATE.exists():
        raise SystemExit(f"Template missing: {TEMPLATE}")
    generated: list[Path] = []
    for version in VERSIONS:
        generated.extend([build_pc_manual(version), build_mobile_manual(version)])
    print("\n".join(str(path) for path in generated))


if __name__ == "__main__":
    main()
