from pathlib import Path

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


ROOT = Path(__file__).resolve().parent
OUT = ROOT / "manual.docx"
SCREENSHOTS = ROOT / "screenshots"


def set_east_asian_font(run, font_name="Microsoft YaHei"):
    run.font.name = font_name
    run._element.rPr.rFonts.set(qn("w:eastAsia"), font_name)


def set_cell_shading(cell, fill):
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:fill"), fill)
    tc_pr.append(shd)


def set_cell_margins(table, top=80, start=120, bottom=80, end=120):
    tbl_pr = table._tbl.tblPr
    tbl_cell_mar = tbl_pr.first_child_found_in("w:tblCellMar")
    if tbl_cell_mar is None:
        tbl_cell_mar = OxmlElement("w:tblCellMar")
        tbl_pr.append(tbl_cell_mar)
    for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tbl_cell_mar.find(qn(f"w:{margin}"))
        if node is None:
            node = OxmlElement(f"w:{margin}")
            tbl_cell_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.set(qn("w:type"), "dxa")


def style_doc(doc):
    section = doc.sections[0]
    section.top_margin = Inches(1)
    section.bottom_margin = Inches(1)
    section.left_margin = Inches(1)
    section.right_margin = Inches(1)

    normal = doc.styles["Normal"]
    normal.font.name = "Microsoft YaHei"
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft YaHei")
    normal.font.size = Pt(11)
    normal.paragraph_format.space_after = Pt(6)
    normal.paragraph_format.line_spacing = 1.25

    for style_name, size, color, before, after in [
        ("Heading 1", 16, "2E74B5", 18, 10),
        ("Heading 2", 13, "2E74B5", 14, 7),
        ("Heading 3", 12, "1F4D78", 10, 5),
    ]:
        style = doc.styles[style_name]
        style.font.name = "Microsoft YaHei"
        style._element.rPr.rFonts.set(qn("w:eastAsia"), "Microsoft YaHei")
        style.font.size = Pt(size)
        style.font.color.rgb = RGBColor.from_string(color)
        style.paragraph_format.space_before = Pt(before)
        style.paragraph_format.space_after = Pt(after)
        style.paragraph_format.line_spacing = 1.25


def para(doc, text="", bold_prefix=None):
    p = doc.add_paragraph()
    if bold_prefix and text.startswith(bold_prefix):
        r = p.add_run(bold_prefix)
        r.bold = True
        set_east_asian_font(r)
        rest = p.add_run(text[len(bold_prefix):])
        set_east_asian_font(rest)
    else:
        r = p.add_run(text)
        set_east_asian_font(r)
    return p


def bullets(doc, items):
    for item in items:
        p = doc.add_paragraph(style="List Bullet")
        r = p.add_run(item)
        set_east_asian_font(r)


def next_numbering_id(numbering, tag):
    values = []
    for node in numbering.findall(qn(tag)):
        attr = "abstractNumId" if tag.endswith("abstractNum") else "numId"
        raw = node.get(qn(f"w:{attr}"))
        if raw is not None:
            values.append(int(raw))
    return max(values, default=0) + 1


def new_decimal_num_id(doc):
    numbering = doc.part.numbering_part.element
    abstract_id = next_numbering_id(numbering, "w:abstractNum")
    num_id = next_numbering_id(numbering, "w:num")

    abstract = OxmlElement("w:abstractNum")
    abstract.set(qn("w:abstractNumId"), str(abstract_id))

    multi = OxmlElement("w:multiLevelType")
    multi.set(qn("w:val"), "singleLevel")
    abstract.append(multi)

    lvl = OxmlElement("w:lvl")
    lvl.set(qn("w:ilvl"), "0")
    start = OxmlElement("w:start")
    start.set(qn("w:val"), "1")
    num_fmt = OxmlElement("w:numFmt")
    num_fmt.set(qn("w:val"), "decimal")
    lvl_text = OxmlElement("w:lvlText")
    lvl_text.set(qn("w:val"), "%1.")
    lvl_jc = OxmlElement("w:lvlJc")
    lvl_jc.set(qn("w:val"), "left")
    p_pr = OxmlElement("w:pPr")
    ind = OxmlElement("w:ind")
    ind.set(qn("w:left"), "540")
    ind.set(qn("w:hanging"), "270")
    p_pr.append(ind)
    for node in (start, num_fmt, lvl_text, lvl_jc, p_pr):
        lvl.append(node)
    abstract.append(lvl)
    numbering.append(abstract)

    num = OxmlElement("w:num")
    num.set(qn("w:numId"), str(num_id))
    ref = OxmlElement("w:abstractNumId")
    ref.set(qn("w:val"), str(abstract_id))
    num.append(ref)
    numbering.append(num)
    return num_id


def attach_num(paragraph, num_id):
    p_pr = paragraph._p.get_or_add_pPr()
    num_pr = OxmlElement("w:numPr")
    ilvl = OxmlElement("w:ilvl")
    ilvl.set(qn("w:val"), "0")
    num = OxmlElement("w:numId")
    num.set(qn("w:val"), str(num_id))
    num_pr.append(ilvl)
    num_pr.append(num)
    p_pr.append(num_pr)


def numbered(doc, items):
    num_id = new_decimal_num_id(doc)
    for item in items:
        p = doc.add_paragraph()
        p.paragraph_format.left_indent = Inches(0.375)
        p.paragraph_format.first_line_indent = Inches(-0.188)
        p.paragraph_format.space_after = Pt(4)
        attach_num(p, num_id)
        r = p.add_run(item)
        set_east_asian_font(r)


def add_figure(doc, image_name, caption):
    image_path = SCREENSHOTS / image_name
    doc.add_picture(str(image_path), width=Inches(6.5))
    last = doc.paragraphs[-1]
    last.alignment = WD_ALIGN_PARAGRAPH.CENTER
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    r = p.add_run(caption)
    r.italic = True
    r.font.size = Pt(9)
    r.font.color.rgb = RGBColor(85, 85, 85)
    set_east_asian_font(r)


def add_table(doc, headers, rows, widths=None):
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.style = "Table Grid"
    set_cell_margins(table)
    hdr = table.rows[0].cells
    for idx, title in enumerate(headers):
        set_cell_shading(hdr[idx], "E8EEF5")
        hdr[idx].vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        run = hdr[idx].paragraphs[0].add_run(title)
        run.bold = True
        set_east_asian_font(run)
    for row in rows:
        cells = table.add_row().cells
        for idx, value in enumerate(row):
            cells[idx].vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            run = cells[idx].paragraphs[0].add_run(value)
            set_east_asian_font(run)
    if widths:
        for row in table.rows:
            for idx, width in enumerate(widths):
                row.cells[idx].width = Inches(width)
    return table


doc = Document()
style_doc(doc)

title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = title.add_run("小票打印机设置操作手册")
r.bold = True
r.font.size = Pt(22)
r.font.color.rgb = RGBColor(31, 77, 120)
set_east_asian_font(r)

subtitle = doc.add_paragraph()
subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = subtitle.add_run("适用功能：系统管理 -> 设备管理 -> Xprinter 打印机")
r.font.size = Pt(10)
r.font.color.rgb = RGBColor(85, 85, 85)
set_east_asian_font(r)

doc.add_heading("这份手册是给谁看的", level=1)
para(doc, "这份手册给餐厅后台管理员、实施人员使用。")
para(doc, "当某个档口需要使用芯烨 XP-C58H 小票打印机时，按这份手册在后台添加打印机，并设置小票是否带二维码、菜品名称字体大小和打印份数。")

doc.add_heading("一句话说明", level=1)
para(doc, "进入 系统管理 -> 设备管理 -> 设备列表，新建一台 Xprinter打印机，填好设备信息、设备 SN、小票模板、打印张数和关联档口，保存后这个档口的订单就会按这台打印机的设置打印小票。")

doc.add_heading("操作前准备", level=1)
para(doc, "开始前先确认 3 件事：")
numbered(doc, [
    "你能登录智慧营养健康餐厅管理系统后台。",
    "你有 系统管理 -> 设备管理 的操作权限。",
    "你手里有打印机的设备 SN，并且知道这台打印机要绑定哪个档口。",
])
para(doc, "如果不知道设备 SN，先找现场实施或设备负责人确认。")

doc.add_heading("进入设备管理", level=1)
numbered(doc, [
    "登录后台。",
    "点击左侧菜单 系统管理。",
    "进入顶部菜单 设备管理。",
    "确认当前页签是 设备列表。",
])
para(doc, "页面右上角可以看到设备类型筛选框和 新建 按钮。")
add_figure(doc, "device-list-xprinter.png", "图 1：设备列表中筛选 Xprinter 打印机")

doc.add_heading("新增小票打印机", level=1)
numbered(doc, [
    "点击右上角 新建。",
    "在弹出的 新建设备 窗口中填写设备信息。",
    "设备类型选择 Xprinter打印机。",
])
add_figure(doc, "device-create-type-dropdown.png", "图 2：新建设备时选择 Xprinter 打印机")
para(doc, "选择后，页面会显示小票打印机专用字段：")
bullets(doc, ["设备SN", "固定型号", "小票模板", "打印张数", "关联档口"])
add_figure(doc, "device-create-xprinter-fields.png", "图 3：Xprinter 打印机专用字段")
para(doc, "按下方说明填写字段，确认无误后点击 保存。")

doc.add_heading("新建设备时怎么填", level=1)
add_table(doc, ["字段", "怎么填", "说明"], [
    ("设备名称", "写一个容易识别的名称", "例如 一楼A档口小票打印机。"),
    ("设备编号", "写后台用的设备编号", "不能和已有设备重复。"),
    ("设备类型", "选择 Xprinter打印机", "选中后才会出现小票模板和打印张数。"),
    ("设备SN", "填打印机机身或设备资料上的 SN", "小票打印机必填。"),
    ("固定型号", "不用填", "系统自动显示 芯烨 XP-C58H。"),
    ("小票模板", "按现场需要选择", "控制二维码和菜品名称字体大小。"),
    ("打印张数", "填每次打印几张", "当前默认 2 张，最少 1 张。"),
    ("设备地址", "可填可不填", "可以写安装位置，例如 一楼A档口收银台旁。"),
    ("关联档口", "选择这台打印机对应的档口", "后续订单会按档口找到这台打印机。"),
], widths=[1.15, 2.05, 3.3])

doc.add_heading("小票模板怎么选", level=1)
add_table(doc, ["你想要的小票效果", "选择这个模板"], [
    ("小票上要二维码，菜品名称大一点", "带二维码-字体大号"),
    ("小票上要二维码，菜品名称正常小一点", "带二维码-字体小号"),
    ("小票上不要二维码，菜品名称大一点", "不带二维码-字体大号"),
    ("小票上不要二维码，菜品名称正常小一点", "不带二维码-字体小号"),
], widths=[3.4, 3.1])
para(doc, "注意：这里的字体大小只影响菜品名称。小票上的餐厅名、取餐号、取餐方式、实付金额等内容不会跟着变小。")

doc.add_heading("保存后怎么确认成功", level=1)
para(doc, "保存成功后，回到设备列表查看这一行：")
numbered(doc, [
    "设备类型显示为 Xprinter打印机。",
    "小票配置显示类似：芯烨 XP-C58H / 带二维码-字体大号 / 2张。",
])
para(doc, "看到这类内容，就说明这台打印机的小票模板和打印张数已经保存到系统里。")

doc.add_heading("修改小票模板或打印张数", level=1)
numbered(doc, [
    "在设备列表找到这台 Xprinter打印机。",
    "点击右侧 编辑。",
    "修改 小票模板 或 打印张数。",
    "点击 保存。",
    "回到列表，查看 小票配置 是否变成新设置。",
])
para(doc, "编辑已有小票打印机时，设备类型和设备SN不能修改。这是为了防止后台记录和真实打印机绑定错乱。")

doc.add_heading("删除小票打印机", level=1)
numbered(doc, [
    "在设备列表找到这台打印机。",
    "点击 删除。",
    "系统弹出确认框后，确认删除。",
])
para(doc, "删除前一定要确认现场不再使用这台打印机。删除后，这个档口可能无法继续通过这台设备打印小票。")

doc.add_heading("常见提示和处理方法", level=1)
add_table(doc, ["提示", "原因", "处理方法"], [
    ("打印机设备，SN必填", "选择了 Xprinter打印机，但没有填写设备 SN。", "补充打印机 SN 后再保存。"),
    ("该档口已绑定小票打印机", "同一个档口已经有一台小票打印机。", "先确认是不是要修改原来的打印机；如果要换设备，先删除或调整原配置。"),
    ("设备编号已存在", "设备编号已经被其他设备使用。", "换一个未使用过的设备编号。"),
    ("设备名称已存在", "设备名称已经被其他设备使用。", "换一个更明确的名称，例如加上档口或楼层。"),
], widths=[1.75, 2.15, 2.6])

doc.add_heading("保存后小票没有打印", level=2)
para(doc, "先检查：")
numbered(doc, [
    "订单对应的档口是否就是这台打印机的 关联档口。",
    "设备 SN 是否填写正确。",
    "设备列表中这台打印机是否还存在。",
    "打印机和网络是否正常。",
])
para(doc, "如果以上都没问题，再联系技术人员查看后台打印日志。")

doc.add_heading("推荐验收步骤", level=1)
numbered(doc, [
    "新增一台测试 Xprinter 打印机。",
    "确认列表 小票配置 显示正确。",
    "编辑这台打印机，换一个小票模板并保存。",
    "再次查看列表，确认模板已更新。",
    "下一个测试订单，确认小票是否按设置打印。",
])

doc.add_heading("当前待确认", level=1)
para(doc, "当前系统里打印张数默认显示为 2 张，最小 1 张。历史需求记录里曾出现默认 1 张的说法，最终以产品确认和系统实现为准。")

doc.core_properties.title = "小票打印机设置操作手册"
doc.core_properties.subject = "智慧营养健康餐厅管理系统 Xprinter 打印机配置"
doc.core_properties.author = "Codex"

doc.save(OUT)
print(OUT)
