from __future__ import annotations

import re
from pathlib import Path

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


ROOT = Path.cwd()
TASK = ROOT / "work/2026-09-28-ai-membership-points"
PRODUCT = ROOT / "modules/product/products/ai-nutritionist/product-solutions/2026-09-24-nutrition-platform/membership-points"
SOURCE = PRODUCT / "competitive-analysis-and-product-plan.md"
STARTER = TASK / "report-working.docx"
OUTPUT = PRODUCT / "AI问答权益点与会员产品竞品分析及规划.docx"

INK = "111111"
DARK = "243247"
BLUE = "2F65C8"
ORANGE = "F58220"
MUTED = "667085"
LINE = "D9D9D9"
PALE = "EEF4FA"
WHITE = "FFFFFF"
CJK = "Hiragino Sans GB"


def set_font(run, size=11.5, bold=False, color=INK, name=CJK):
    run.font.name = name
    rfonts = run._element.get_or_add_rPr().get_or_add_rFonts()
    for key in ("ascii", "hAnsi", "eastAsia", "cs"):
        rfonts.set(qn(f"w:{key}"), name)
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.color.rgb = RGBColor.from_string(color)
    return run


def style_doc(doc):
    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = CJK
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), CJK)
    normal.font.size = Pt(11.5)
    normal.paragraph_format.line_spacing = 1.42
    normal.paragraph_format.space_after = Pt(6)
    if "Title" not in styles:
        styles.add_style("Title", 1)
    for name, size, before, after in (("Title", 26, 0, 14), ("Heading 1", 18, 18, 8), ("Heading 2", 14, 14, 6), ("Heading 3", 12, 10, 4)):
        if name not in styles:
            styles.add_style(name, 1)
        s = styles[name]
        s.font.name = CJK
        s._element.rPr.rFonts.set(qn("w:eastAsia"), CJK)
        s.font.size = Pt(size)
        s.font.bold = True
        s.font.color.rgb = RGBColor.from_string(INK)
        s.paragraph_format.space_before = Pt(before)
        s.paragraph_format.space_after = Pt(after)
        s.paragraph_format.keep_with_next = True


def add_page_number(paragraph):
    paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
    begin = OxmlElement("w:fldChar"); begin.set(qn("w:fldCharType"), "begin")
    instr = OxmlElement("w:instrText"); instr.set(qn("xml:space"), "preserve"); instr.text = " PAGE "
    end = OxmlElement("w:fldChar"); end.set(qn("w:fldCharType"), "end")
    run = paragraph.add_run(); run._r.extend([begin, instr, end]); set_font(run, size=9, color=MUTED)


def cell_shade(cell, fill):
    tcpr = cell._tc.get_or_add_tcPr()
    shd = tcpr.find(qn("w:shd")) or OxmlElement("w:shd")
    shd.set(qn("w:fill"), fill)
    if shd.getparent() is None:
        tcpr.append(shd)


def cell_border(cell):
    tcpr = cell._tc.get_or_add_tcPr()
    borders = tcpr.first_child_found_in("w:tcBorders")
    if borders is None:
        borders = OxmlElement("w:tcBorders"); tcpr.append(borders)
    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"), "6"); node.set(qn("w:color"), LINE)
        borders.append(node)


def cell_margins(cell):
    tcpr = cell._tc.get_or_add_tcPr()
    mar = tcpr.first_child_found_in("w:tcMar")
    if mar is None:
        mar = OxmlElement("w:tcMar"); tcpr.append(mar)
    for key, val in (("top", 100), ("start", 115), ("bottom", 100), ("end", 115)):
        node = OxmlElement(f"w:{key}"); node.set(qn("w:w"), str(val)); node.set(qn("w:type"), "dxa"); mar.append(node)


def mark_header(row):
    trpr = row._tr.get_or_add_trPr(); node = OxmlElement("w:tblHeader"); node.set(qn("w:val"), "true"); trpr.append(node)


def parse_inline(paragraph, text, size=11.5, color=INK):
    text = re.sub(r"<([^>]+)>", r"\1", text)
    chunks = re.split(r"(\*\*.*?\*\*|`.*?`)", text)
    for chunk in chunks:
        if not chunk:
            continue
        if chunk.startswith("**") and chunk.endswith("**"):
            set_font(paragraph.add_run(chunk[2:-2]), size=size, bold=True, color=color)
        elif chunk.startswith("`") and chunk.endswith("`"):
            set_font(paragraph.add_run(chunk[1:-1]), size=max(size - 0.5, 9), color=BLUE)
        else:
            set_font(paragraph.add_run(chunk), size=size, color=color)


def add_table(doc, rows):
    width = len(rows[0])
    table = doc.add_table(rows=1, cols=width)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = True
    mark_header(table.rows[0])
    for i, value in enumerate(rows[0]):
        c = table.rows[0].cells[i]; cell_shade(c, DARK); cell_border(c); cell_margins(c); c.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
        p = c.paragraphs[0]; p.alignment = WD_ALIGN_PARAGRAPH.CENTER; p.paragraph_format.space_after = Pt(0); parse_inline(p, value, 9.2, WHITE)
    for ridx, row in enumerate(rows[1:]):
        cells = table.add_row().cells
        for i, value in enumerate(row):
            c = cells[i]; cell_shade(c, PALE if ridx % 2 else WHITE); cell_border(c); cell_margins(c); c.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
            p = c.paragraphs[0]; p.paragraph_format.space_after = Pt(0); p.paragraph_format.line_spacing = 1.15
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i == 0 else WD_ALIGN_PARAGRAPH.LEFT
            parse_inline(p, value, 8.9)
    spacer = doc.add_paragraph(); spacer.paragraph_format.space_after = Pt(2)


def picture_size(path: Path):
    with Image.open(path) as im:
        w, h = im.size
    ratio = h / w
    max_w, max_h = 5.7, 7.3
    width = min(max_w, max_h / ratio)
    return max(width, 1.6)


def add_image(doc, alt, rel_path):
    path = (PRODUCT / rel_path).resolve()
    if not path.exists():
        return
    insert_path = path
    if path.suffix.lower() == ".webp":
        converted = TASK / f"converted-{path.stem}.png"
        with Image.open(path) as image:
            image.convert("RGB").save(converted, "PNG")
        insert_path = converted
    p = doc.add_paragraph(); p.alignment = WD_ALIGN_PARAGRAPH.CENTER; p.paragraph_format.keep_with_next = True
    shape = p.add_run().add_picture(str(insert_path), width=Inches(picture_size(path)))
    shape._inline.docPr.set("descr", alt); shape._inline.docPr.set("title", alt)
    cap = doc.add_paragraph(); cap.alignment = WD_ALIGN_PARAGRAPH.CENTER; cap.paragraph_format.space_after = Pt(10)
    set_font(cap.add_run(alt), size=9.2, color=MUTED)


def strip_table_line(line):
    return [x.strip() for x in line.strip().strip("|").split("|")]


def build():
    doc = Document(str(STARTER)); style_doc(doc)
    body = doc._element.body
    for child in list(body):
        if child.tag != qn("w:sectPr"):
            body.remove(child)
    for section in doc.sections:
        section.top_margin = Cm(2.54); section.bottom_margin = Cm(2.2); section.left_margin = Cm(3.175); section.right_margin = Cm(3.175)
        add_page_number(section.footer.paragraphs[0])
        for paragraph in section.header.paragraphs:
            for run in paragraph.runs:
                set_font(run, size=12, bold=True)
        for node in section.header._element.iter(qn("wp:docPr")):
            node.set("descr", "康比特品牌标志"); node.set("title", "康比特品牌标志")

    # cover, no reporting metadata fields
    p = doc.add_paragraph(); p.alignment = WD_ALIGN_PARAGRAPH.CENTER; p.paragraph_format.space_before = Pt(105)
    set_font(p.add_run("AI积分与会员产品竞品分析及规划"), size=25, bold=True)
    sub = doc.add_paragraph(); sub.alignment = WD_ALIGN_PARAGRAPH.CENTER; sub.paragraph_format.space_before = Pt(8)
    set_font(sub.add_run("基于标准产品现状的会员积分机制与页面实施建议"), size=14, color=MUTED)
    intro = doc.add_paragraph(); intro.paragraph_format.space_before = Pt(48); intro.paragraph_format.left_indent = Cm(1); intro.paragraph_format.right_indent = Cm(1)
    parse_inline(intro, "核心建议：非会员AI问答每日最多3次且每月最多15次，文案、深度、图片问答每次均计1次；菜品识别每日1次，首次完整体重方案1次并缓存。完整会员每月发放100通用积分，完整方案10积分且每30天最多新生成1次；首月9.9元/月、后续22.9元/月手动续购，不自动续费。", 12.3, DARK)
    doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)

    lines = SOURCE.read_text("utf-8").splitlines()
    i = 0
    first_h1 = True
    while i < len(lines):
        line = lines[i].rstrip()
        if not line:
            i += 1; continue
        if line.startswith("# "):
            if first_h1:
                first_h1 = False
            else:
                doc.add_heading(line[2:], level=1)
            i += 1; continue
        if line.startswith("## "):
            doc.add_heading(line[3:], level=1); i += 1; continue
        if line.startswith("### "):
            doc.add_heading(line[4:], level=2); i += 1; continue
        m = re.match(r"!\[(.*?)\]\((.*?)\)", line)
        if m:
            add_image(doc, m.group(1), m.group(2)); i += 1; continue
        if line.lstrip().startswith("<img "):
            i += 1; continue
        if line.startswith("|") and i + 1 < len(lines) and re.match(r"^\|?[\s:|-]+\|", lines[i + 1]):
            table_rows = [strip_table_line(line)]
            i += 2
            while i < len(lines) and lines[i].startswith("|"):
                table_rows.append(strip_table_line(lines[i])); i += 1
            add_table(doc, table_rows); continue
        if re.match(r"^[-*] ", line):
            p = doc.add_paragraph(); p.paragraph_format.left_indent = Cm(.72); p.paragraph_format.first_line_indent = Cm(-.38); p.paragraph_format.space_after = Pt(4)
            set_font(p.add_run("• "), bold=True, color=ORANGE); parse_inline(p, line[2:]); i += 1; continue
        if re.match(r"^\d+\. ", line):
            p = doc.add_paragraph(); p.paragraph_format.left_indent = Cm(.72); p.paragraph_format.first_line_indent = Cm(-.45); p.paragraph_format.space_after = Pt(4)
            num, text = line.split(". ", 1); set_font(p.add_run(f"{num}、"), bold=True, color=ORANGE); parse_inline(p, text); i += 1; continue
        if line.startswith("```"):
            i += 1; code=[]
            while i < len(lines) and not lines[i].startswith("```"):
                code.append(lines[i]); i += 1
            i += 1
            p=doc.add_paragraph(); p.paragraph_format.left_indent=Cm(.7); p.paragraph_format.right_indent=Cm(.7)
            set_font(p.add_run("\n".join(code)), size=9.5, color=BLUE); continue
        para = [line]; i += 1
        while i < len(lines) and lines[i].strip() and not re.match(r"^(#|\||[-*] |\d+\. |!\[|```)", lines[i]):
            para.append(lines[i].strip()); i += 1
        p = doc.add_paragraph(); p.paragraph_format.first_line_indent = Cm(.74); p.paragraph_format.line_spacing = 1.42
        parse_inline(p, " ".join(para))

    doc.core_properties.title = "AI积分与会员产品竞品分析及规划"
    doc.core_properties.subject = "标准产品AI权益点与会员产品规划"
    doc.core_properties.author = "康比特数字体育科技"
    doc.core_properties.last_modified_by = "康比特数字体育科技"
    doc.core_properties.keywords = "AI问答,会员,积分,权益点,竞品分析,产品规划"
    doc.save(str(OUTPUT))
    print(OUTPUT)


if __name__ == "__main__":
    build()
