from pathlib import Path
import os
import tempfile
import xml.etree.ElementTree as ET
import zipfile

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


ROOT = Path(__file__).resolve().parent
STARTER = ROOT / "assets" / "kangbite-starter.docx"
SOURCE_DIR = ROOT.parent / "VWCG-1119-VWCG-1121-launch-announcement-20260730"
OUTPUT = ROOT / "智慧营养健康餐厅-充值赠送与消费限制操作手册-20260730.docx"

IMG_GIFT_ADMIN = SOURCE_DIR / "system-account-gift-rules.png"
IMG_GIFT_MOBILE = SOURCE_DIR / "system-mobile-recharge-gift.png"
IMG_LIMIT_ADMIN = SOURCE_DIR / "system-consume-limit-account-scope.png"

NAVY = "173558"
BLUE = "2E78D5"
LIGHT_BLUE = "EFF6FF"
ORANGE = "F36C21"
LIGHT_ORANGE = "FFF4EA"
INK = "27364B"
MUTED = "64748B"
LINE = "D9E2EC"
PALE = "F7F9FC"
WHITE = "FFFFFF"
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"


def set_cell_shading(cell, fill):
    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:fill"), fill)


def set_cell_border(cell, **kwargs):
    tc = cell._tc
    tc_pr = tc.get_or_add_tcPr()
    borders = tc_pr.first_child_found_in("w:tcBorders")
    if borders is None:
        borders = OxmlElement("w:tcBorders")
        tc_pr.append(borders)
    for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
        if edge not in kwargs:
            continue
        edge_data = kwargs.get(edge)
        tag = "w:{}".format(edge)
        element = borders.find(qn(tag))
        if element is None:
            element = OxmlElement(tag)
            borders.append(element)
        for key in ["val", "sz", "space", "color"]:
            if key in edge_data:
                element.set(qn("w:{}".format(key)), str(edge_data[key]))


def set_cell_margins(cell, top=120, start=140, bottom=120, end=140):
    tc = cell._tc
    tc_pr = tc.get_or_add_tcPr()
    tc_mar = tc_pr.first_child_found_in("w:tcMar")
    if tc_mar is None:
        tc_mar = OxmlElement("w:tcMar")
        tc_pr.append(tc_mar)
    for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{margin}"))
        if node is None:
            node = OxmlElement(f"w:{margin}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.set(qn("w:type"), "dxa")


def set_repeat_table_header(row):
    tr_pr = row._tr.get_or_add_trPr()
    tbl_header = OxmlElement("w:tblHeader")
    tbl_header.set(qn("w:val"), "true")
    tr_pr.append(tbl_header)


def prevent_row_split(row):
    tr_pr = row._tr.get_or_add_trPr()
    cant_split = OxmlElement("w:cantSplit")
    tr_pr.append(cant_split)


def set_keep_with_next(paragraph, value=True):
    p_pr = paragraph._p.get_or_add_pPr()
    keep_next = p_pr.find(qn("w:keepNext"))
    if keep_next is None:
        keep_next = OxmlElement("w:keepNext")
        p_pr.append(keep_next)
    keep_next.set(qn("w:val"), "1" if value else "0")


def set_keep_lines(paragraph, value=True):
    p_pr = paragraph._p.get_or_add_pPr()
    keep_lines = p_pr.find(qn("w:keepLines"))
    if keep_lines is None:
        keep_lines = OxmlElement("w:keepLines")
        p_pr.append(keep_lines)
    keep_lines.set(qn("w:val"), "1" if value else "0")


def add_bottom_border(paragraph, color=ORANGE, size=12, space=4):
    p_pr = paragraph._p.get_or_add_pPr()
    p_bdr = p_pr.find(qn("w:pBdr"))
    if p_bdr is None:
        p_bdr = OxmlElement("w:pBdr")
        p_pr.append(p_bdr)
    bottom = OxmlElement("w:bottom")
    bottom.set(qn("w:val"), "single")
    bottom.set(qn("w:sz"), str(size))
    bottom.set(qn("w:space"), str(space))
    bottom.set(qn("w:color"), color)
    p_bdr.append(bottom)


def set_repeat_header(section):
    section.header.is_linked_to_previous = False


def set_font(run, name="PingFang SC", size=10.5, color=INK, bold=False, italic=False):
    run.font.name = name
    run._element.rPr.rFonts.set(qn("w:eastAsia"), name)
    run._element.rPr.rFonts.set(qn("w:ascii"), name)
    run._element.rPr.rFonts.set(qn("w:hAnsi"), name)
    run.font.size = Pt(size)
    run.font.color.rgb = RGBColor.from_string(color)
    run.bold = bold
    run.italic = italic
    return run


def style_paragraph(paragraph, *, before=0, after=5, line=1.35, align=None):
    fmt = paragraph.paragraph_format
    fmt.space_before = Pt(before)
    fmt.space_after = Pt(after)
    fmt.line_spacing = line
    if align is not None:
        paragraph.alignment = align
    return paragraph


def add_text(paragraph, text, **kwargs):
    return set_font(paragraph.add_run(text), **kwargs)


def add_heading(document, text, level=1, number=None):
    paragraph = document.add_paragraph()
    paragraph.style = document.styles[f"Heading {level}"]
    style_paragraph(paragraph, before=8 if level == 1 else 5, after=8 if level == 1 else 5, line=1.15)
    set_keep_with_next(paragraph)
    if number:
        run = add_text(
            paragraph,
            f"{number}  ",
            size=16 if level == 1 else 12,
            color=ORANGE,
            bold=True,
        )
        run.font.name = "Arial"
    add_text(
        paragraph,
        text,
        size=18 if level == 1 else 13,
        color=NAVY,
        bold=True,
    )
    if level == 1:
        add_bottom_border(paragraph, color=ORANGE, size=10, space=5)
    return paragraph


def add_body(document, text, *, bold_prefix=None, after=5, indent=0, color=INK):
    p = document.add_paragraph()
    style_paragraph(p, after=after, line=1.45)
    if indent:
        p.paragraph_format.left_indent = Cm(indent)
    if bold_prefix and text.startswith(bold_prefix):
        add_text(p, bold_prefix, bold=True, color=color)
        add_text(p, text[len(bold_prefix):], color=color)
    else:
        add_text(p, text, color=color)
    return p


def add_bullet(document, text, *, level=0, accent=ORANGE):
    table = document.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.LEFT
    table.autofit = False
    table.columns[0].width = Cm(0.45)
    table.columns[1].width = Cm(15.0 - level * 0.4)
    table.rows[0].cells[0].width = Cm(0.45)
    table.rows[0].cells[1].width = Cm(15.0 - level * 0.4)
    table.rows[0].cells[0].vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP
    for cell in table.rows[0].cells:
        set_cell_margins(cell, top=20, start=0, bottom=20, end=0)
        set_cell_border(
            cell,
            top={"val": "nil"},
            bottom={"val": "nil"},
            left={"val": "nil"},
            right={"val": "nil"},
        )
    p1 = table.cell(0, 0).paragraphs[0]
    style_paragraph(p1, after=0, line=1.3, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p1, "●", size=6.5, color=accent, bold=True)
    p2 = table.cell(0, 1).paragraphs[0]
    style_paragraph(p2, after=0, line=1.35)
    add_text(p2, text, size=10.2)
    return table


def add_callout(document, title, body, *, kind="info"):
    colors = {
        "info": (LIGHT_BLUE, BLUE),
        "warning": (LIGHT_ORANGE, ORANGE),
        "neutral": (PALE, NAVY),
    }
    fill, accent = colors[kind]
    table = document.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    table.columns[0].width = Cm(0.35)
    table.columns[1].width = Cm(15.1)
    left, content = table.rows[0].cells
    left.width = Cm(0.35)
    content.width = Cm(15.1)
    set_cell_shading(left, accent)
    set_cell_shading(content, fill)
    set_cell_margins(left, top=80, start=0, bottom=80, end=0)
    set_cell_margins(content, top=110, start=170, bottom=110, end=170)
    for cell in (left, content):
        set_cell_border(
            cell,
            top={"val": "single", "sz": 4, "color": fill},
            bottom={"val": "single", "sz": 4, "color": fill},
            left={"val": "single", "sz": 4, "color": fill},
            right={"val": "single", "sz": 4, "color": fill},
        )
    p = content.paragraphs[0]
    style_paragraph(p, after=2, line=1.2)
    add_text(p, title, size=10.5, color=accent, bold=True)
    p2 = content.add_paragraph()
    style_paragraph(p2, after=0, line=1.35)
    add_text(p2, body, size=9.7, color=INK)
    return table


def add_step(document, number, title, body):
    table = document.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    table.columns[0].width = Cm(1.15)
    table.columns[1].width = Cm(14.3)
    num_cell, content_cell = table.rows[0].cells
    num_cell.width = Cm(1.15)
    content_cell.width = Cm(14.3)
    num_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
    content_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
    set_cell_shading(num_cell, ORANGE)
    set_cell_shading(content_cell, WHITE)
    set_cell_margins(num_cell, top=125, start=50, bottom=125, end=50)
    set_cell_margins(content_cell, top=105, start=170, bottom=105, end=170)
    for cell in (num_cell, content_cell):
        set_cell_border(
            cell,
            top={"val": "single", "sz": 7, "color": LINE},
            bottom={"val": "single", "sz": 7, "color": LINE},
            left={"val": "single", "sz": 7, "color": LINE},
            right={"val": "single", "sz": 7, "color": LINE},
        )
    p_num = num_cell.paragraphs[0]
    style_paragraph(p_num, after=0, line=1, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p_num, str(number).zfill(2), name="Arial", size=12.5, color=WHITE, bold=True)
    p = content_cell.paragraphs[0]
    style_paragraph(p, after=2, line=1.2)
    add_text(p, title, size=10.6, color=NAVY, bold=True)
    p2 = content_cell.add_paragraph()
    style_paragraph(p2, after=0, line=1.35)
    add_text(p2, body, size=9.6, color=MUTED)
    spacer = document.add_paragraph()
    style_paragraph(spacer, after=0, line=0.15)
    return table


def add_caption(document, text):
    p = document.add_paragraph()
    style_paragraph(p, before=3, after=7, line=1.2, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p, text, size=8.5, color=MUTED)
    return p


def add_picture(document, path, width, alt_text):
    p = document.add_paragraph()
    style_paragraph(p, before=3, after=0, line=1, align=WD_ALIGN_PARAGRAPH.CENTER)
    run = p.add_run()
    shape = run.add_picture(str(path), width=width)
    shape._inline.docPr.set("descr", alt_text)
    shape._inline.docPr.set("title", alt_text)
    c_nv_pr = shape._inline.graphic.graphicData.pic.nvPicPr.cNvPr
    c_nv_pr.set("descr", alt_text)
    c_nv_pr.set("title", alt_text)
    return p


def add_page_break(document):
    p = document.add_paragraph()
    p.add_run().add_break(WD_BREAK.PAGE)


def add_footer(section):
    footer = section.footer
    footer.is_linked_to_previous = False
    for paragraph in footer.paragraphs:
        paragraph.clear()
    table = footer.add_table(rows=1, cols=2, width=Inches(6.0))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    table.columns[0].width = Inches(4.9)
    table.columns[1].width = Inches(1.1)
    for cell in table.rows[0].cells:
        set_cell_margins(cell, top=0, start=0, bottom=0, end=0)
        set_cell_border(
            cell,
            top={"val": "single", "sz": 5, "color": LINE},
            bottom={"val": "nil"},
            left={"val": "nil"},
            right={"val": "nil"},
        )
    left = table.cell(0, 0).paragraphs[0]
    style_paragraph(left, before=3, after=0, line=1)
    add_text(left, "www.cptpro.cn", size=8, color=MUTED)
    right = table.cell(0, 1).paragraphs[0]
    style_paragraph(right, before=3, after=0, line=1, align=WD_ALIGN_PARAGRAPH.RIGHT)
    add_text(right, "第 ", size=8, color=MUTED)
    run = right.add_run()
    fld_char = OxmlElement("w:fldChar")
    fld_char.set(qn("w:fldCharType"), "begin")
    instr_text = OxmlElement("w:instrText")
    instr_text.set(qn("xml:space"), "preserve")
    instr_text.text = " PAGE "
    fld_char2 = OxmlElement("w:fldChar")
    fld_char2.set(qn("w:fldCharType"), "end")
    run._r.append(fld_char)
    run._r.append(instr_text)
    run._r.append(fld_char2)
    set_font(run, size=8, color=MUTED)
    add_text(right, " 页", size=8, color=MUTED)


def configure_styles(document):
    normal = document.styles["Normal"]
    normal.font.name = "PingFang SC"
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
    normal._element.rPr.rFonts.set(qn("w:ascii"), "PingFang SC")
    normal._element.rPr.rFonts.set(qn("w:hAnsi"), "PingFang SC")
    normal.font.size = Pt(10.5)
    normal.font.color.rgb = RGBColor.from_string(INK)
    normal.paragraph_format.line_spacing = 1.35
    normal.paragraph_format.space_after = Pt(5)
    for level, size in ((1, 18), (2, 13), (3, 11)):
        style = document.styles[f"Heading {level}"]
        style.font.name = "PingFang SC"
        style._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
        style._element.rPr.rFonts.set(qn("w:ascii"), "PingFang SC")
        style._element.rPr.rFonts.set(qn("w:hAnsi"), "PingFang SC")
        style.font.size = Pt(size)
        style.font.bold = True
        style.font.color.rgb = RGBColor.from_string(NAVY)
        style.paragraph_format.keep_with_next = True
        style.paragraph_format.keep_together = True
    try:
        title_style = document.styles["Title"]
    except KeyError:
        title_style = None
    if title_style is not None:
        title_style.font.name = "PingFang SC"
        title_style._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")


def ensure_font_table_entry(docx_path, font_name):
    """Declare the selected CJK font in fontTable.xml for stricter DOCX renderers."""
    ET.register_namespace("w", W_NS)
    with zipfile.ZipFile(docx_path, "r") as src:
        font_table = ET.fromstring(src.read("word/fontTable.xml"))
        font_attr = f"{{{W_NS}}}name"
        if any(node.get(font_attr) == font_name for node in font_table.findall(f"{{{W_NS}}}font")):
            return
        font = ET.SubElement(font_table, f"{{{W_NS}}}font", {font_attr: font_name})
        ET.SubElement(font, f"{{{W_NS}}}altName", {f"{{{W_NS}}}val": font_name})
        ET.SubElement(font, f"{{{W_NS}}}charset", {f"{{{W_NS}}}val": "86"})
        ET.SubElement(font, f"{{{W_NS}}}family", {f"{{{W_NS}}}val": "swiss"})
        ET.SubElement(font, f"{{{W_NS}}}pitch", {f"{{{W_NS}}}val": "variable"})
        font_table_xml = ET.tostring(font_table, encoding="utf-8", xml_declaration=True)
        header_xml = None
        if "word/header1.xml" in src.namelist():
            header = ET.fromstring(src.read("word/header1.xml"))
            for node in header.findall(f".//{{{WP_NS}}}docPr"):
                node.set("descr", "CPT 康比特品牌标识")
                node.set("title", "CPT 康比特品牌标识")
            header_xml = ET.tostring(header, encoding="utf-8", xml_declaration=True)
        fd, temp_name = tempfile.mkstemp(suffix=".docx", dir=str(docx_path.parent))
        os.close(fd)
        temp_path = Path(temp_name)
        try:
            with zipfile.ZipFile(temp_path, "w", zipfile.ZIP_DEFLATED) as dst:
                for item in src.infolist():
                    if item.filename == "word/fontTable.xml":
                        data = font_table_xml
                    elif item.filename == "word/header1.xml" and header_xml is not None:
                        data = header_xml
                    else:
                        data = src.read(item.filename)
                    dst.writestr(item, data)
            os.replace(temp_path, docx_path)
            os.chmod(docx_path, 0o644)
        finally:
            if temp_path.exists():
                temp_path.unlink()


def add_cover(document):
    p = document.add_paragraph()
    style_paragraph(p, before=34, after=8, line=1, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p, "智慧营养健康餐厅", size=16, color=ORANGE, bold=True)

    p = document.add_paragraph()
    style_paragraph(p, before=22, after=8, line=1.12, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p, "充值赠送与消费限制", size=28, color=NAVY, bold=True)
    p2 = document.add_paragraph()
    style_paragraph(p2, after=20, line=1.1, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p2, "操作手册", size=28, color=NAVY, bold=True)

    p = document.add_paragraph()
    style_paragraph(p, after=24, line=1.2, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p, "适用于 PC 管理后台与微信小程序", size=11.5, color=MUTED)

    table = document.add_table(rows=3, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    table.columns[0].width = Cm(4.1)
    table.columns[1].width = Cm(8.5)
    rows = [
        ("适用角色", "系统管理员、运营人员、充值用户"),
        ("手册版本", "V1.0"),
        ("发布日期", "2026 年 7 月 30 日"),
    ]
    for i, (label, value) in enumerate(rows):
        left, right = table.rows[i].cells
        left.width = Cm(4.1)
        right.width = Cm(8.5)
        left.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        right.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        set_cell_shading(left, LIGHT_ORANGE)
        set_cell_shading(right, PALE)
        for cell in (left, right):
            set_cell_margins(cell, top=140, start=170, bottom=140, end=170)
            set_cell_border(
                cell,
                top={"val": "single", "sz": 5, "color": WHITE},
                bottom={"val": "single", "sz": 5, "color": WHITE},
                left={"val": "single", "sz": 5, "color": WHITE},
                right={"val": "single", "sz": 5, "color": WHITE},
            )
        p_l = left.paragraphs[0]
        p_r = right.paragraphs[0]
        style_paragraph(p_l, after=0, line=1.2)
        style_paragraph(p_r, after=0, line=1.2)
        add_text(p_l, label, size=10, color=ORANGE, bold=True)
        add_text(p_r, value, size=10, color=INK)

    p = document.add_paragraph()
    style_paragraph(p, before=72, after=4, line=1, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p, "北京康比特体育科技股份有限公司", size=10.5, color=NAVY, bold=True)
    p = document.add_paragraph()
    style_paragraph(p, after=0, line=1, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(p, "客户操作资料", size=9, color=MUTED)


def add_overview(document):
    add_heading(document, "使用说明", level=1, number="01")
    add_body(
        document,
        "本手册用于指导客户完成现金充值赠送规则、账户消费顺序、用户端充值确认，以及消费限制中的受限账户配置。",
    )
    add_callout(
        document,
        "使用前提与验证范围",
        "管理员账号需具备账户设置或消费限制菜单权限；相关功能已在客户环境启用。界面依据 2026 年 7 月 30 日提供的 PC 管理后台与微信小程序截图核对，实际字段以客户当前版本和权限配置为准。",
        kind="neutral",
    )
    add_callout(
        document,
        "先判断由谁操作",
        "管理员负责配置规则；运营人员负责上线前核验；用户仅需在充值页确认金额和余额。不同角色不需要阅读全部章节。",
        kind="info",
    )

    add_heading(document, "快速导航", level=2)
    nav = document.add_table(rows=4, cols=3)
    nav.alignment = WD_TABLE_ALIGNMENT.CENTER
    nav.autofit = False
    widths = [Cm(2.3), Cm(5.0), Cm(8.1)]
    for row in nav.rows:
        prevent_row_split(row)
        for i, cell in enumerate(row.cells):
            cell.width = widths[i]
            set_cell_margins(cell, top=115, start=130, bottom=115, end=130)
            set_cell_border(
                cell,
                top={"val": "single", "sz": 5, "color": LINE},
                bottom={"val": "single", "sz": 5, "color": LINE},
                left={"val": "single", "sz": 5, "color": LINE},
                right={"val": "single", "sz": 5, "color": LINE},
            )
    nav_data = [
        ("管理员", "配置充值赠送", "账户管理 → 账户设置 → 现金充值设置"),
        ("管理员", "设置扣款顺序", "账户管理 → 账户设置 → 消费顺序设置"),
        ("用户", "查看充值结果", "微信小程序 → 钱包/充值"),
        ("管理员", "配置消费限制", "营销中心 → 消费限制 → 新增/编辑"),
    ]
    for r, row_data in enumerate(nav_data):
        for c, text in enumerate(row_data):
            cell = nav.cell(r, c)
            set_cell_shading(cell, LIGHT_ORANGE if c == 0 else WHITE)
            p = cell.paragraphs[0]
            style_paragraph(p, after=0, line=1.25)
            add_text(
                p,
                text,
                size=9.4,
                color=ORANGE if c == 0 else INK,
                bold=(c == 0 or c == 1),
            )

    add_heading(document, "三个账户分别表示什么", level=2)
    terms = document.add_table(rows=1, cols=3)
    terms.alignment = WD_TABLE_ALIGNMENT.CENTER
    terms.autofit = False
    term_data = [
        ("个人账户", "用户自有资金账户，可按现有业务规则使用。"),
        ("餐补账户", "单位或组织发放的餐补资金账户。"),
        ("赠送账户", "充值活动产生的赠送资金账户，可消费，不可提现或转赠。"),
    ]
    for i, (title, body) in enumerate(term_data):
        cell = terms.cell(0, i)
        cell.width = Cm(5.1)
        set_cell_shading(cell, PALE if i != 2 else LIGHT_ORANGE)
        set_cell_margins(cell, top=150, start=150, bottom=150, end=150)
        set_cell_border(
            cell,
            top={"val": "single", "sz": 5, "color": WHITE},
            bottom={"val": "single", "sz": 5, "color": WHITE},
            left={"val": "single", "sz": 5, "color": WHITE},
            right={"val": "single", "sz": 5, "color": WHITE},
        )
        p = cell.paragraphs[0]
        style_paragraph(p, after=4, line=1.2)
        add_text(p, title, size=10.2, color=ORANGE if i == 2 else NAVY, bold=True)
        p2 = cell.add_paragraph()
        style_paragraph(p2, after=0, line=1.35)
        add_text(p2, body, size=9, color=MUTED)

    add_callout(
        document,
        "截图说明",
        "本手册截图用于说明页面位置和字段含义；截图中的金额、规则名称、更新时间等均为示例，不代表客户正式配置。",
        kind="warning",
    )


def add_gift_admin_screenshot(document):
    add_heading(document, "配置现金充值赠送规则", level=1, number="02")
    add_body(
        document,
        "入口：账户管理 → 账户设置 → 现金充值设置。管理员可新增或编辑固定金额赠送规则。",
        bold_prefix="入口：",
    )
    add_picture(
        document,
        IMG_GIFT_ADMIN,
        Inches(6.05),
        "PC 管理后台现金充值设置页面，包含规则列表、新增规则及现金充值设置页签。",
    )
    add_caption(document, "图 1　现金充值设置页面（示例数据）")
    add_callout(
        document,
        "页面识别重点",
        "确认顶部选中“现金充值设置”；列表中可查看支付方式、满额金额、赠送金额、状态及更新时间。",
        kind="info",
    )
    add_heading(document, "字段速查", level=2)
    table = document.add_table(rows=2, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    cards = [
        ("支付方式", "限定哪些充值渠道可命中本条规则。"),
        ("满额金额", "用户实付金额达到该门槛后才参与赠送。"),
        ("赠送金额", "命中规则后进入赠送账户的固定金额。"),
        ("状态", "只有启用状态的规则参与计算。"),
    ]
    for idx, (title, body) in enumerate(cards):
        cell = table.cell(idx // 2, idx % 2)
        cell.width = Cm(7.65)
        set_cell_shading(cell, LIGHT_ORANGE if idx % 2 == 0 else PALE)
        set_cell_margins(cell, top=95, start=150, bottom=95, end=150)
        set_cell_border(
            cell,
            top={"val": "single", "sz": 5, "color": WHITE},
            bottom={"val": "single", "sz": 5, "color": WHITE},
            left={"val": "single", "sz": 5, "color": WHITE},
            right={"val": "single", "sz": 5, "color": WHITE},
        )
        p = cell.paragraphs[0]
        style_paragraph(p, after=2, line=1.15)
        add_text(p, title, size=9.5, color=ORANGE if idx % 2 == 0 else NAVY, bold=True)
        p2 = cell.add_paragraph()
        style_paragraph(p2, after=0, line=1.25)
        add_text(p2, body, size=8.8, color=MUTED)


def add_gift_admin_steps(document):
    add_heading(document, "现金充值赠送：操作步骤", level=1, number="03")
    add_step(document, 1, "进入账户设置", "在 PC 管理后台依次进入“账户管理 → 账户设置”。")
    add_step(document, 2, "打开现金充值设置", "选择“现金充值设置”页签，查看已创建的充值赠送规则。")
    add_step(document, 3, "新增或编辑规则", "点击“新增规则”，或在目标规则右侧点击“编辑”。")
    add_step(
        document,
        4,
        "填写规则内容",
        "填写规则名称、适用支付方式、满额金额、赠送金额和状态。页面支持固定金额赠送。",
    )
    add_step(document, 5, "保存并核验", "保存后确认规则状态为“启用”，并核对金额与支付方式是否符合运营方案。")

    add_heading(document, "规则生效逻辑", level=2)
    add_bullet(document, "用户实付金额达到规则门槛，并使用规则支持的支付方式时，系统计算赠送金额。")
    add_bullet(document, "未命中有效规则时，赠送金额为 0 元，实际到账金额等于实付金额。")
    add_bullet(document, "赠送金额进入独立的赠送账户；该余额可以消费，不可提现或转赠。")
    add_bullet(document, "本期仅支持固定金额赠送，不包含按比例赠送、积分、优惠券、有效期等扩展玩法。")
    add_callout(
        document,
        "上线前核验",
        "建议用一笔满足门槛和一笔不满足门槛的测试充值分别验证，确认赠送金额与实际到账金额均符合预期。",
        kind="warning",
    )


def add_consumption_order(document):
    add_heading(document, "设置账户消费顺序", level=1, number="04")
    add_body(
        document,
        "入口：账户管理 → 账户设置 → 消费顺序设置。该配置决定新产生的消费订单优先从哪个账户扣款。",
        bold_prefix="入口：",
    )
    add_step(document, 1, "打开消费顺序设置", "进入账户设置后，选择“消费顺序设置”页签。")
    add_step(
        document,
        2,
        "设置三个扣款顺位",
        "依次选择第一、第二、第三顺位。餐补账户、赠送账户、个人账户必须全部出现，且不能重复。",
    )
    add_step(document, 3, "核对并保存", "确认顺位符合客户的结算政策后保存。保存成功后再返回页面复核一次。")
    add_step(document, 4, "验证新订单", "用新产生的消费订单验证扣款去向；历史订单不会因顺序调整而重新计算。")

    table = document.add_table(rows=4, cols=3)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    widths = [Cm(2.3), Cm(4.2), Cm(9.0)]
    headers = ("顺位", "账户", "说明")
    for i, header in enumerate(headers):
        cell = table.cell(0, i)
        cell.width = widths[i]
        set_cell_shading(cell, NAVY)
        p = cell.paragraphs[0]
        style_paragraph(p, after=0, line=1.2, align=WD_ALIGN_PARAGRAPH.CENTER)
        add_text(p, header, size=9.5, color=WHITE, bold=True)
    order_data = [
        ("第一顺位", "餐补账户", "优先使用组织发放的餐补余额。"),
        ("第二顺位", "赠送账户", "再使用充值活动产生的赠送余额。"),
        ("第三顺位", "个人账户", "最后使用用户个人余额补足。"),
    ]
    for r, row_data in enumerate(order_data, 1):
        for c, text in enumerate(row_data):
            cell = table.cell(r, c)
            cell.width = widths[c]
            set_cell_shading(cell, LIGHT_ORANGE if c == 0 else WHITE)
            set_cell_margins(cell, top=115, start=130, bottom=115, end=130)
            set_cell_border(
                cell,
                top={"val": "single", "sz": 5, "color": LINE},
                bottom={"val": "single", "sz": 5, "color": LINE},
                left={"val": "single", "sz": 5, "color": LINE},
                right={"val": "single", "sz": 5, "color": LINE},
            )
            p = cell.paragraphs[0]
            style_paragraph(p, after=0, line=1.25, align=WD_ALIGN_PARAGRAPH.CENTER if c < 2 else None)
            add_text(p, text, size=9.3, color=ORANGE if c == 0 else INK, bold=(c < 2))
    add_caption(document, "表 1　常用扣款顺序示例；客户可按实际业务政策调整")
    add_callout(
        document,
        "重要",
        "修改消费顺序只影响保存后新产生的订单，不改变历史订单和已完成的资金流水。",
        kind="warning",
    )


def add_user_recharge(document):
    add_heading(document, "用户端充值与到账确认", level=1, number="05")
    add_body(
        document,
        "用户在微信小程序的钱包/充值页面，可同时查看个人余额、餐补余额和赠送余额，并在支付前确认赠送金额及实际到账金额。",
    )
    table = document.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    left, right = table.rows[0].cells
    left.width = Cm(6.2)
    right.width = Cm(9.2)
    for cell in (left, right):
        set_cell_margins(cell, top=50, start=100, bottom=50, end=100)
        set_cell_border(
            cell,
            top={"val": "nil"},
            bottom={"val": "nil"},
            left={"val": "nil"},
            right={"val": "nil"},
        )
    p = left.paragraphs[0]
    style_paragraph(p, after=0, line=1, align=WD_ALIGN_PARAGRAPH.CENTER)
    run = p.add_run()
    shape = run.add_picture(str(IMG_GIFT_MOBILE), width=Inches(2.35))
    shape._inline.docPr.set(
        "descr",
        "微信小程序充值页面，显示三个账户余额、充值金额、赠送金额和实际到账金额。",
    )
    shape._inline.docPr.set(
        "title",
        "微信小程序充值页面，显示三个账户余额、充值金额、赠送金额和实际到账金额。",
    )
    shape._inline.graphic.graphicData.pic.nvPicPr.cNvPr.set(
        "descr",
        "微信小程序充值页面，显示三个账户余额、充值金额、赠送金额和实际到账金额。",
    )
    shape._inline.graphic.graphicData.pic.nvPicPr.cNvPr.set(
        "title",
        "微信小程序充值页面，显示三个账户余额、充值金额、赠送金额和实际到账金额。",
    )
    items = [
        ("1", "查看账户余额", "页面顶部展示个人余额、餐补余额、赠送余额。"),
        ("2", "选择充值金额", "选择预设金额或输入允许的充值金额。"),
        ("3", "确认赠送金额", "系统按当前启用的充值活动计算赠送金额。"),
        ("4", "核对实际到账", "实际到账金额＝充值金额＋赠送金额。"),
        ("5", "立即充值", "确认金额无误后提交支付，并在支付完成后复核余额。"),
    ]
    right.paragraphs[0]._element.getparent().remove(right.paragraphs[0]._element)
    for number, title, body in items:
        p = right.add_paragraph()
        style_paragraph(p, after=2, line=1.2)
        add_text(p, f"{number}  ", name="Arial", size=10.5, color=ORANGE, bold=True)
        add_text(p, title, size=10.5, color=NAVY, bold=True)
        p2 = right.add_paragraph()
        style_paragraph(p2, after=7, line=1.35)
        add_text(p2, body, size=9.4, color=MUTED)
    add_caption(document, "图 2　用户端充值页面（示例金额）")
    add_callout(
        document,
        "资金规则",
        "赠送余额进入独立赠送账户，可用于消费，不可提现或转赠；未命中赠送规则时，页面显示赠送金额 0 元。",
        kind="warning",
    )


def add_limit_screenshot(document):
    add_heading(document, "配置消费限制的受限账户", level=1, number="06")
    add_body(
        document,
        "入口：营销中心 → 消费限制 → 新增/编辑消费限制。管理员可指定范围限制或次数限制作用于哪些账户。",
        bold_prefix="入口：",
    )
    add_picture(
        document,
        IMG_LIMIT_ADMIN,
        Inches(6.05),
        "PC 管理后台新增消费限制页面，显示受限账户多选项和消费限制类型。",
    )
    add_caption(document, "图 3　新增消费限制页面（示例配置）")
    add_callout(
        document,
        "页面识别重点",
        "“受限账户”为必填多选项，可选个人账户、餐补账户、赠送账户；至少选择一个账户后才能保存。",
        kind="info",
    )
    add_heading(document, "账户选择结果", level=2)
    table = document.add_table(rows=1, cols=2)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    cards = [
        ("已勾选账户", "受本条规则的范围限制或次数限制影响。"),
        ("未勾选账户", "不受本条规则的范围或次数限制影响。"),
    ]
    for idx, (title, body) in enumerate(cards):
        cell = table.cell(0, idx)
        cell.width = Cm(7.65)
        set_cell_shading(cell, LIGHT_ORANGE if idx == 0 else PALE)
        set_cell_margins(cell, top=125, start=160, bottom=125, end=160)
        set_cell_border(
            cell,
            top={"val": "single", "sz": 5, "color": WHITE},
            bottom={"val": "single", "sz": 5, "color": WHITE},
            left={"val": "single", "sz": 5, "color": WHITE},
            right={"val": "single", "sz": 5, "color": WHITE},
        )
        p = cell.paragraphs[0]
        style_paragraph(p, after=3, line=1.2)
        add_text(p, title, size=9.8, color=ORANGE if idx == 0 else NAVY, bold=True)
        p2 = cell.add_paragraph()
        style_paragraph(p2, after=0, line=1.3)
        add_text(p2, body, size=9, color=MUTED)


def add_limit_steps(document):
    add_heading(document, "消费限制：操作步骤", level=1, number="07")
    add_step(document, 1, "填写基础信息", "填写消费限制名称、选择部门，并确认应用场景。")
    add_step(
        document,
        2,
        "选择受限账户",
        "在“受限账户”中勾选个人账户、餐补账户、赠送账户中的一个或多个。该项至少选择一个。",
    )
    add_step(document, 3, "选择限制类型", "按业务需要选择“范围限制”或“次数限制”。")
    add_step(
        document,
        4,
        "完成限制配置",
        "范围限制需配置受限场所与餐别；次数限制需按现有页面填写次数等条件。",
    )
    add_step(document, 5, "保存并复核", "保存后重新进入规则，确认受限账户、限制类型和状态正确回显。")

    add_heading(document, "判断规则", level=2)
    rules = document.add_table(rows=3, cols=2)
    rules.alignment = WD_TABLE_ALIGNMENT.CENTER
    rules.autofit = False
    rules.columns[0].width = Cm(4.5)
    rules.columns[1].width = Cm(10.9)
    rule_data = [
        ("已勾选账户", "受所配置的范围限制或次数限制影响。"),
        ("未勾选账户", "不受本条规则的范围或次数限制影响。"),
        ("字段提示语", "所选账户受范围或次数的限制。"),
    ]
    for r, (left_text, right_text) in enumerate(rule_data):
        for c, text in enumerate((left_text, right_text)):
            cell = rules.cell(r, c)
            set_cell_shading(cell, LIGHT_ORANGE if c == 0 else WHITE)
            set_cell_margins(cell, top=125, start=150, bottom=125, end=150)
            set_cell_border(
                cell,
                top={"val": "single", "sz": 5, "color": LINE},
                bottom={"val": "single", "sz": 5, "color": LINE},
                left={"val": "single", "sz": 5, "color": LINE},
                right={"val": "single", "sz": 5, "color": LINE},
            )
            p = cell.paragraphs[0]
            style_paragraph(p, after=0, line=1.3)
            add_text(p, text, size=9.5, color=ORANGE if c == 0 else INK, bold=(c == 0))

    add_callout(
        document,
        "配置示例",
        "仅勾选“个人账户”和“餐补账户”时，规则只限制这两个账户；赠送账户仍可按其他有效规则正常消费。",
        kind="info",
    )


def add_checklist_faq(document):
    add_heading(document, "上线核验清单", level=1, number="08")
    checklist = document.add_table(rows=1, cols=3)
    checklist.alignment = WD_TABLE_ALIGNMENT.CENTER
    checklist.autofit = False
    widths = [Cm(1.4), Cm(10.8), Cm(3.2)]
    headers = ("序号", "核验内容", "结果")
    for i, header in enumerate(headers):
        cell = checklist.cell(0, i)
        cell.width = widths[i]
        set_cell_shading(cell, NAVY)
        set_cell_margins(cell, top=95, start=120, bottom=95, end=120)
        p = cell.paragraphs[0]
        style_paragraph(p, after=0, line=1.2, align=WD_ALIGN_PARAGRAPH.CENTER)
        add_text(p, header, size=9.3, color=WHITE, bold=True)
    set_repeat_table_header(checklist.rows[0])
    checks = [
        "充值赠送规则的支付方式、门槛、赠送金额和启用状态正确。",
        "满足门槛的测试充值能显示赠送金额和正确的实际到账金额。",
        "未满足门槛的测试充值显示赠送金额 0 元。",
        "赠送金额进入赠送账户，且不可提现或转赠。",
        "三个账户的消费顺序无重复、无遗漏，并与客户政策一致。",
        "消费限制至少选择一个受限账户，保存后可正确回显。",
        "已选账户受到限制，未选账户不受该条规则影响。",
    ]
    for idx, text in enumerate(checks, 1):
        row = checklist.add_row()
        prevent_row_split(row)
        for c, value in enumerate((str(idx), text, "□ 通过  □ 待处理")):
            cell = row.cells[c]
            cell.width = widths[c]
            set_cell_shading(cell, LIGHT_ORANGE if c == 0 else WHITE)
            set_cell_margins(cell, top=90, start=120, bottom=90, end=120)
            set_cell_border(
                cell,
                top={"val": "single", "sz": 5, "color": LINE},
                bottom={"val": "single", "sz": 5, "color": LINE},
                left={"val": "single", "sz": 5, "color": LINE},
                right={"val": "single", "sz": 5, "color": LINE},
            )
            p = cell.paragraphs[0]
            style_paragraph(
                p,
                after=0,
                line=1.25,
                align=WD_ALIGN_PARAGRAPH.CENTER if c != 1 else None,
            )
            add_text(p, value, size=8.9, color=ORANGE if c == 0 else INK, bold=(c == 0))

    add_heading(document, "常见问题", level=2)
    faqs = [
        ("充值后为什么没有赠送金额？", "请检查规则是否启用、支付方式是否匹配，以及实付金额是否达到门槛。"),
        ("消费顺序为什么无法保存？", "请确认个人、餐补、赠送三个账户均已选择，且没有重复。"),
        ("消费限制为什么无法保存？", "“受限账户”为必填项，至少需要选择一个账户。"),
        ("未勾选的账户为什么还能消费？", "这是正常逻辑：未勾选账户不受该条范围或次数限制影响。"),
        ("赠送账户余额能提现吗？", "不能。赠送余额仅用于消费，不可提现或转赠。"),
    ]
    for question, answer in faqs:
        p = document.add_paragraph()
        style_paragraph(p, before=3, after=2, line=1.2)
        set_keep_with_next(p)
        add_text(p, f"Q　{question}", size=9.8, color=NAVY, bold=True)
        p2 = document.add_paragraph()
        style_paragraph(p2, after=5, line=1.35)
        add_text(p2, f"A　{answer}", size=9.3, color=MUTED)

    add_callout(
        document,
        "使用提示",
        "如客户存在特殊结算政策，请先确认资金归属、扣款顺序和限制范围，再调整系统配置；调整后应通过新订单验证。",
        kind="warning",
    )


def build():
    missing = [path for path in (STARTER, IMG_GIFT_ADMIN, IMG_GIFT_MOBILE, IMG_LIMIT_ADMIN) if not path.exists()]
    if missing:
        raise FileNotFoundError("Missing required files: " + ", ".join(map(str, missing)))

    document = Document(str(STARTER))
    configure_styles(document)

    section = document.sections[0]
    section.page_width = Cm(21.0)
    section.page_height = Cm(29.7)
    section.top_margin = Cm(2.55)
    section.bottom_margin = Cm(1.8)
    section.left_margin = Cm(2.55)
    section.right_margin = Cm(2.55)
    section.header_distance = Cm(0.8)
    section.footer_distance = Cm(0.65)
    set_repeat_header(section)
    for paragraph in section.header.paragraphs:
        for run in paragraph.runs:
            set_font(
                run,
                size=run.font.size.pt if run.font.size else 9,
                color=INK,
                bold=bool(run.bold),
            )
    add_footer(section)

    if document.paragraphs and not document.paragraphs[0].text:
        p = document.paragraphs[0]
        p._element.getparent().remove(p._element)

    add_cover(document)
    add_page_break(document)
    add_overview(document)
    add_page_break(document)
    add_gift_admin_screenshot(document)
    add_page_break(document)
    add_gift_admin_steps(document)
    add_page_break(document)
    add_consumption_order(document)
    add_page_break(document)
    add_user_recharge(document)
    add_page_break(document)
    add_limit_screenshot(document)
    add_page_break(document)
    add_limit_steps(document)
    add_page_break(document)
    add_checklist_faq(document)

    core = document.core_properties
    core.title = "智慧营养健康餐厅—充值赠送与消费限制操作手册"
    core.subject = "VWCG-1121、VWCG-1119 客户操作手册"
    core.author = "北京康比特体育科技股份有限公司"
    core.keywords = "智慧营养健康餐厅,充值赠送,赠送账户,消费顺序,消费限制"
    core.comments = "客户操作手册 V1.0，发布日期 2026-07-30"

    document.save(str(OUTPUT))
    ensure_font_table_entry(OUTPUT, "PingFang SC")
    print(f"Generated: {OUTPUT}")


if __name__ == "__main__":
    build()
