from pathlib import Path
import re
import tempfile
import zipfile

from docx import Document
from docx.enum.style import WD_STYLE_TYPE
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 Inches, Pt, RGBColor


ROOT = Path(__file__).resolve().parent
SOURCE_MD = ROOT / "manual.md"
WORKING_COPY = ROOT / "working-copy.docx"
OUTPUT = ROOT / "manual.docx"

FONT_NAME = "Noto Sans CJK SC"
ACCENT = "2F5F96"
LIGHT_GRAY = "D9D9D9"
PALE_BLUE = "F5F9FD"


def set_font(run, size=10.5, bold=False, color="000000"):
    run.font.name = FONT_NAME
    rpr = run._element.get_or_add_rPr()
    rpr.rFonts.set(qn("w:eastAsia"), FONT_NAME)
    rpr.rFonts.set(qn("w:ascii"), FONT_NAME)
    rpr.rFonts.set(qn("w:hAnsi"), FONT_NAME)
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.color.rgb = RGBColor.from_string(color)


def set_cell_margins(cell, top=95, start=110, bottom=95, end=110):
    tc_pr = cell._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 name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{name}"))
        if node is None:
            node = OxmlElement(f"w:{name}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.set(qn("w:type"), "dxa")


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


def set_table_borders(table):
    borders = OxmlElement("w:tblBorders")
    for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
        node = OxmlElement(f"w:{edge}")
        node.set(qn("w:val"), "single")
        node.set(qn("w:sz"), "4")
        node.set(qn("w:color"), LIGHT_GRAY)
        borders.append(node)
    table._tbl.tblPr.append(borders)


def add_inline_text(paragraph, text, size=10.5, color="000000", bold=False):
    parts = re.split(r"(\*\*.+?\*\*|`.+?`)", text)
    for part in parts:
        if not part:
            continue
        is_bold = part.startswith("**") and part.endswith("**")
        is_code = part.startswith("`") and part.endswith("`")
        clean = part[2:-2] if is_bold else (part[1:-1] if is_code else part)
        run = paragraph.add_run(clean)
        set_font(run, size=size, bold=(bold or is_bold), color=color)
        if is_code:
            run.font.name = "Menlo"
            run._element.get_or_add_rPr().rFonts.set(qn("w:ascii"), "Menlo")
            run._element.get_or_add_rPr().rFonts.set(qn("w:hAnsi"), "Menlo")


def add_paragraph(doc, text="", size=10.5, color="000000", bold=False, before=0, after=5, align=None):
    paragraph = doc.add_paragraph()
    if align is not None:
        paragraph.alignment = align
    paragraph.paragraph_format.space_before = Pt(before)
    paragraph.paragraph_format.space_after = Pt(after)
    paragraph.paragraph_format.line_spacing = 1.35
    paragraph.paragraph_format.keep_together = True
    add_inline_text(paragraph, text, size=size, color=color, bold=bold)
    return paragraph


def add_heading(doc, text, level):
    style_name = {1: "Heading 1", 2: "Heading 2", 3: "Heading 3"}[level]
    size = {1: 16, 2: 13, 3: 11.5}[level]
    paragraph = doc.add_paragraph()
    paragraph.style = doc.styles[style_name]
    paragraph.paragraph_format.keep_with_next = True
    paragraph.paragraph_format.space_before = Pt(10 if level == 1 else 7)
    paragraph.paragraph_format.space_after = Pt(5)
    add_inline_text(paragraph, text, size=size, bold=True)
    return paragraph


def add_list_item(doc, text, number=None):
    prefix = f"{number}. " if number is not None else "• "
    paragraph = add_paragraph(doc, prefix + text, size=10.2, after=3)
    paragraph.paragraph_format.left_indent = Inches(0.18)
    paragraph.paragraph_format.first_line_indent = Inches(-0.18)
    return paragraph


def default_widths(column_count):
    if column_count == 2:
        return [1.45, 4.0]
    if column_count == 3:
        return [1.2, 2.45, 1.8]
    if column_count == 4:
        return [1.0, 1.8, 1.3, 1.35]
    return [5.45 / column_count] * column_count


def add_table(doc, headers, rows, widths=None):
    widths = widths or default_widths(len(headers))
    table = doc.add_table(rows=1, cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    set_table_borders(table)
    tr_pr = table.rows[0]._tr.get_or_add_trPr()
    repeat = OxmlElement("w:tblHeader")
    repeat.set(qn("w:val"), "true")
    tr_pr.append(repeat)
    for index, value in enumerate(headers):
        cell = table.rows[0].cells[index]
        cell.width = Inches(widths[index])
        shade_cell(cell, ACCENT)
        set_cell_margins(cell)
        cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
        paragraph = cell.paragraphs[0]
        paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
        paragraph.paragraph_format.space_after = Pt(0)
        add_inline_text(paragraph, value, size=9, color="FFFFFF", bold=True)
    for row_index, values in enumerate(rows):
        cells = table.add_row().cells
        for index, value in enumerate(values):
            cell = cells[index]
            cell.width = Inches(widths[index])
            if row_index % 2:
                shade_cell(cell, PALE_BLUE)
            set_cell_margins(cell)
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
            paragraph = cell.paragraphs[0]
            paragraph.paragraph_format.space_after = Pt(0)
            paragraph.paragraph_format.line_spacing = 1.2
            paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER if index == 0 else WD_ALIGN_PARAGRAPH.LEFT
            add_inline_text(paragraph, value, size=9)
    spacer = doc.add_paragraph()
    spacer.paragraph_format.space_after = Pt(2)
    return table


def add_figure(doc, alt_text, relative_path):
    path = ROOT / relative_path
    paragraph = doc.add_paragraph()
    paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
    paragraph.paragraph_format.keep_with_next = True
    picture = paragraph.add_run().add_picture(str(path), width=Inches(5.65))
    picture._inline.docPr.set("descr", alt_text)
    picture._inline.docPr.set("title", alt_text)


def parse_table(lines, start):
    def cells(line):
        return [part.strip() for part in line.strip().strip("|").split("|")]

    headers = cells(lines[start])
    index = start + 2
    rows = []
    while index < len(lines) and lines[index].strip().startswith("|"):
        rows.append(cells(lines[index]))
        index += 1
    return headers, rows, index


def restore_brand_parts(output, source):
    preserve = {
        "word/header1.xml",
        "word/_rels/header1.xml.rels",
        "word/media/image1.png",
    }
    with zipfile.ZipFile(source, "r") as source_zip, zipfile.ZipFile(output, "r") as final_zip:
        parts = {name: final_zip.read(name) for name in final_zip.namelist()}
        for name in preserve:
            parts[name] = source_zip.read(name)
    with tempfile.NamedTemporaryFile(dir=ROOT, suffix=".docx", delete=False) as handle:
        temp_path = Path(handle.name)
    try:
        with zipfile.ZipFile(temp_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
            for name, payload in parts.items():
                archive.writestr(name, payload)
        temp_path.replace(output)
    finally:
        if temp_path.exists():
            temp_path.unlink()


lines = SOURCE_MD.read_text(encoding="utf-8").splitlines()
doc = Document(WORKING_COPY)
for paragraph in list(doc.paragraphs):
    if not paragraph.text.strip():
        doc._element.body.remove(paragraph._element)

styles = doc.styles
if "Title" not in styles:
    title_style = styles.add_style("Title", WD_STYLE_TYPE.PARAGRAPH)
    title_style.base_style = styles["Normal"]
for style_name in ("Normal", "Title", "Heading 1", "Heading 2", "Heading 3"):
    style = styles[style_name]
    style.font.name = FONT_NAME
    style._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), FONT_NAME)
    style.font.color.rgb = RGBColor(0, 0, 0)

doc.core_properties.title = "AI营养智慧食堂管理平台 PC端操作手册 膳食营养模块"
doc.core_properties.author = "康比特数字体育科技"
doc.core_properties.last_modified_by = "康比特数字体育科技"

title_text = lines[0][2:].strip()
cover_brand = add_paragraph(doc, "AI营养智慧食堂管理平台", size=13, color=ACCENT, after=10, align=WD_ALIGN_PARAGRAPH.CENTER)
cover_brand.paragraph_format.space_before = Pt(48)
title = add_paragraph(doc, "PC端操作手册", size=24, bold=True, after=8, align=WD_ALIGN_PARAGRAPH.CENTER)
title.style = doc.styles["Title"]
subtitle = add_paragraph(doc, "膳食营养模块", size=20, color=ACCENT, bold=True, after=24, align=WD_ALIGN_PARAGRAPH.CENTER)

metadata = []
first_heading = 1
for index in range(1, len(lines)):
    stripped = lines[index].strip()
    if stripped.startswith("## "):
        first_heading = index
        break
    if "：" in stripped:
        key, value = stripped.split("：", 1)
        if key in {"版本", "适用角色", "最后验证"}:
            metadata.append((key, value))
add_table(doc, ["项目", "内容"], metadata, widths=[1.2, 4.25])
add_paragraph(doc, "本手册依据当前模块线程、已验证代码和本地真实页面编制。截图使用测试数据，不含个人敏感信息。", size=10, color="555555", before=14)
doc.add_page_break()

index = first_heading
while index < len(lines):
    raw = lines[index]
    line = raw.strip()
    if not line:
        index += 1
        continue
    if line == "<!-- pagebreak -->":
        doc.add_page_break()
        index += 1
        continue
    if line.startswith("#### "):
        add_heading(doc, line[5:].strip(), 3)
        index += 1
        continue
    if line.startswith("### "):
        add_heading(doc, line[4:].strip(), 2)
        index += 1
        continue
    if line.startswith("## "):
        add_heading(doc, line[3:].strip(), 1)
        index += 1
        continue
    image_match = re.match(r"!\[(.*?)\]\((.*?)\)", line)
    if image_match:
        add_figure(doc, image_match.group(1), image_match.group(2))
        index += 1
        continue
    if re.match(r"^图\d+\s", line):
        caption = add_paragraph(doc, line, size=9, color="666666", after=8, align=WD_ALIGN_PARAGRAPH.CENTER)
        caption.paragraph_format.keep_together = True
        index += 1
        continue
    if line.startswith("|") and index + 1 < len(lines) and re.match(r"^\|?\s*:?-{3,}", lines[index + 1].strip()):
        headers, rows, index = parse_table(lines, index)
        add_table(doc, headers, rows)
        continue
    ordered = re.match(r"^(\d+)\.\s+(.*)$", line)
    if ordered:
        add_list_item(doc, ordered.group(2), number=int(ordered.group(1)))
        index += 1
        continue
    if line.startswith("- "):
        add_list_item(doc, line[2:])
        index += 1
        continue
    add_paragraph(doc, line)
    index += 1

doc.save(OUTPUT)
restore_brand_parts(OUTPUT, WORKING_COPY)
OUTPUT.chmod(0o644)
print(f"Generated {OUTPUT}")
