#!/usr/bin/env python3
import html
import re
import shutil
import struct
from pathlib import Path
from zipfile import ZipFile, ZIP_DEFLATED


WORKDIR = Path("/Applications/MAMP/htdocs/toolchat")
SOURCE_MD = WORKDIR / "HUONG_DAN_SU_DUNG_FLYCHAT.md"
REFERENCE_DOCX = Path("/Users/quangqa/.codex/plugins/cache/openai-curated-remote/openai-templates/0.1.1/skills/artifact-template-strategy-memorandum/assets/reference.docx")
OUTPUT_DOCX = WORKDIR / "HUONG_DAN_SU_DUNG_FLYCHAT.docx"

NS = (
    'xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" '
    'xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" '
    'xmlns:o="urn:schemas-microsoft-com:office:office" '
    'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" '
    'xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" '
    'xmlns:v="urn:schemas-microsoft-com:vml" '
    'xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" '
    'xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" '
    'xmlns:w10="urn:schemas-microsoft-com:office:word" '
    'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" '
    'xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" '
    'xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" '
    'xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" '
    'xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" '
    'xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" '
    'mc:Ignorable="w14 wp14"'
)

SECT_PR = (
    '<w:sectPr><w:headerReference w:type="default" r:id="rId9"/>'
    '<w:footerReference w:type="default" r:id="rId10"/>'
    '<w:pgSz w:w="12240" w:h="15840"/>'
    '<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="705" w:footer="708" w:gutter="0"/>'
    '<w:pgNumType w:start="1"/><w:cols w:space="720"/></w:sectPr>'
)


def esc(text):
    return html.escape(text, quote=False)


def image_size(path):
    with path.open("rb") as f:
        sig = f.read(65536)
    if sig[:8] != b"\x89PNG\r\n\x1a\n":
        if sig[:2] == b"\xff\xd8":
            idx = 2
            while idx + 9 < len(sig):
                if sig[idx] != 0xFF:
                    idx += 1
                    continue
                marker = sig[idx + 1]
                idx += 2
                if marker in (0xD8, 0xD9):
                    continue
                size = int.from_bytes(sig[idx:idx + 2], "big")
                if marker in range(0xC0, 0xC4):
                    return struct.unpack(">HH", sig[idx + 3:idx + 7])[1], struct.unpack(">HH", sig[idx + 3:idx + 7])[0]
                idx += size
        raise ValueError(f"Unsupported screenshot format: {path}")
    return struct.unpack(">II", sig[16:24])


def text_runs(markdown_text):
    text = markdown_text.replace("**", "")
    parts = re.split(r"(`[^`]+`)", text)
    runs = []
    for part in parts:
        if not part:
            continue
        mono = part.startswith("`") and part.endswith("`")
        value = part[1:-1] if mono else part
        font = '<w:rFonts w:ascii="Courier New" w:hAnsi="Courier New" w:eastAsia="Courier New"/>' if mono else ""
        runs.append(f'<w:r><w:rPr>{font}</w:rPr><w:t xml:space="preserve">{esc(value)}</w:t></w:r>')
    return "".join(runs)


def paragraph(text="", style=None, bold=False, italic=False, color=None, before=0, after=120, line=276, align=None):
    ppr = []
    if style:
        ppr.append(f'<w:pStyle w:val="{style}"/>')
    if align:
        ppr.append(f'<w:jc w:val="{align}"/>')
    ppr.append(f'<w:spacing w:before="{before}" w:after="{after}" w:line="{line}" w:lineRule="auto"/>')
    rpr = []
    if bold:
        rpr.append("<w:b/>")
    if italic:
        rpr.append("<w:i/>")
    if color:
        rpr.append(f'<w:color w:val="{color}"/>')
    run = f'<w:r><w:rPr>{"".join(rpr)}</w:rPr><w:t xml:space="preserve">{esc(text)}</w:t></w:r>' if (bold or italic or color) else text_runs(text)
    return f'<w:p><w:pPr>{"".join(ppr)}</w:pPr>{run}</w:p>'


def bullet(text, level=0):
    left = 720 + level * 360
    hanging = 360
    return (
        f'<w:p><w:pPr><w:pStyle w:val="ListParagraph"/>'
        f'<w:spacing w:after="80" w:line="276" w:lineRule="auto"/>'
        f'<w:ind w:left="{left}" w:hanging="{hanging}"/></w:pPr>'
        f'<w:r><w:t>•</w:t></w:r><w:r><w:t xml:space="preserve"> </w:t></w:r>{text_runs(text)}</w:p>'
    )


def numbered(text, n, level=0):
    left = 720 + level * 360
    hanging = 360
    return (
        f'<w:p><w:pPr><w:pStyle w:val="ListParagraph"/>'
        f'<w:spacing w:after="80" w:line="276" w:lineRule="auto"/>'
        f'<w:ind w:left="{left}" w:hanging="{hanging}"/></w:pPr>'
        f'<w:r><w:t>{n}.</w:t></w:r><w:r><w:t xml:space="preserve"> </w:t></w:r>{text_runs(text)}</w:p>'
    )


def quote(text):
    return (
        '<w:p><w:pPr><w:pBdr><w:left w:val="single" w:sz="16" w:space="8" w:color="5B7C99"/></w:pBdr>'
        '<w:shd w:val="clear" w:color="auto" w:fill="F3F6F8"/>'
        '<w:ind w:left="240"/><w:spacing w:before="80" w:after="160" w:line="276" w:lineRule="auto"/></w:pPr>'
        f'{text_runs(text)}</w:p>'
    )


def table(rows):
    widths = [2300, 2600, 4460]
    grid = "".join(f'<w:gridCol w:w="{w}"/>' for w in widths)
    trs = []
    for idx, row in enumerate(rows):
        cells = []
        for cidx, cell in enumerate(row):
            fill = "D9EAF7" if idx == 0 else "FFFFFF"
            bold = "<w:b/>" if idx == 0 else ""
            cells.append(
                f'<w:tc><w:tcPr><w:tcW w:w="{widths[cidx]}" w:type="dxa"/>'
                f'<w:shd w:val="clear" w:color="auto" w:fill="{fill}"/>'
                '<w:tcMar><w:top w:w="100" w:type="dxa"/><w:left w:w="120" w:type="dxa"/>'
                '<w:bottom w:w="100" w:type="dxa"/><w:right w:w="120" w:type="dxa"/></w:tcMar></w:tcPr>'
                f'<w:p><w:pPr><w:spacing w:after="0"/></w:pPr><w:r><w:rPr>{bold}</w:rPr><w:t>{esc(cell)}</w:t></w:r></w:p></w:tc>'
            )
        trs.append(f'<w:tr>{"".join(cells)}</w:tr>')
    return (
        '<w:tbl><w:tblPr><w:tblW w:w="9360" w:type="dxa"/>'
        '<w:tblBorders><w:top w:val="single" w:sz="4" w:color="C8D2DC"/>'
        '<w:left w:val="single" w:sz="4" w:color="C8D2DC"/><w:bottom w:val="single" w:sz="4" w:color="C8D2DC"/>'
        '<w:right w:val="single" w:sz="4" w:color="C8D2DC"/><w:insideH w:val="single" w:sz="4" w:color="C8D2DC"/>'
        '<w:insideV w:val="single" w:sz="4" w:color="C8D2DC"/></w:tblBorders></w:tblPr>'
        f'<w:tblGrid>{grid}</w:tblGrid>{"".join(trs)}</w:tbl>'
    )


def image_paragraph(rid, path, target_name, caption):
    px_w, px_h = image_size(path)
    max_cx = 5_943_600
    cx = min(max_cx, int(px_w * 914400 / 144))
    cy = int(cx * px_h / px_w)
    docpr_id = int(rid[3:]) + 100
    drawing = f'''
    <w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0">
      <wp:extent cx="{cx}" cy="{cy}"/><wp:effectExtent l="0" t="0" r="0" b="0"/>
      <wp:docPr id="{docpr_id}" name="{esc(caption)}"/><wp:cNvGraphicFramePr/>
      <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
        <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
          <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
            <pic:nvPicPr><pic:cNvPr id="0" name="{esc(target_name)}"/><pic:cNvPicPr/></pic:nvPicPr>
            <pic:blipFill><a:blip r:embed="{rid}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>
            <pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="{cx}" cy="{cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr>
          </pic:pic>
        </a:graphicData>
      </a:graphic>
    </wp:inline></w:drawing>'''
    return (
        '<w:p><w:pPr><w:jc w:val="center"/><w:spacing w:before="160" w:after="60"/></w:pPr>'
        f'<w:r>{drawing}</w:r></w:p>'
        f'{paragraph(caption, italic=True, color="667085", after=180, align="center")}'
    )


def parse_md(lines):
    body = []
    image_refs = []
    table_rows = []
    list_stack = []
    in_table = False
    for raw in lines:
        line = raw.rstrip("\n")
        if not line.strip():
            if in_table and table_rows:
                body.append(table(table_rows))
                table_rows = []
                in_table = False
            continue
        if line.startswith("|") and line.endswith("|"):
            cells = [c.strip() for c in line.strip("|").split("|")]
            if all(set(c) <= {"-", ":"} for c in cells):
                continue
            table_rows.append(cells)
            in_table = True
            continue
        if in_table and table_rows:
            body.append(table(table_rows))
            table_rows = []
            in_table = False
        m = re.match(r"!\[(.*?)\]\((.*?)\)", line)
        if m:
            caption, rel = m.groups()
            rid = f"rId{20 + len(image_refs)}"
            img_path = (WORKDIR / rel).resolve()
            target_name = f"flychat-screenshot-{len(image_refs) + 1:02d}.jpg"
            image_refs.append((rid, img_path, target_name))
            body.append(image_paragraph(rid, img_path, target_name, caption))
            continue
        h = re.match(r"^(#{1,3})\s+(.+)$", line)
        if h:
            level = len(h.group(1))
            style = "Title" if level == 1 else f"Heading{min(level, 3)}"
            body.append(paragraph(h.group(2), style=style, bold=(level == 1), before=180 if level > 1 else 0, after=160))
            continue
        if line.startswith("> "):
            body.append(quote(line[2:]))
            continue
        m = re.match(r"^(\s*)-\s+(.+)$", line)
        if m:
            body.append(bullet(m.group(2), len(m.group(1)) // 2))
            continue
        m = re.match(r"^(\s*)(\d+)\.\s+(.+)$", line)
        if m:
            body.append(numbered(m.group(3), int(m.group(2)), len(m.group(1)) // 3))
            continue
        if line.strip() == "---":
            body.append(paragraph("", after=80))
            continue
        body.append(paragraph(line, after=140))
    if table_rows:
        body.append(table(table_rows))
    return body, image_refs


def rels_xml(existing, image_refs):
    end = "</Relationships>"
    additions = "".join(
        f'<Relationship Id="{rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/{target_name}"/>'
        for rid, path, target_name in image_refs
    )
    return existing.replace(end, additions + end)


def content_types_xml(existing):
    if 'Extension="jpg"' in existing:
        return existing
    marker = '<Default Extension="png" ContentType="image/png"/>'
    addition = marker + '<Default Extension="jpg" ContentType="image/jpeg"/><Default Extension="jpeg" ContentType="image/jpeg"/>'
    return existing.replace(marker, addition)


def header_xml(existing):
    return existing.replace("Strategy Memo", "FlyChat User Guide")


def main():
    lines = SOURCE_MD.read_text(encoding="utf-8").splitlines()
    body, image_refs = parse_md(lines)
    document_xml = f"<?xml version='1.0' encoding='UTF-8' standalone='yes'?><w:document {NS}><w:body>{''.join(body)}{SECT_PR}</w:body></w:document>"

    tmp = OUTPUT_DOCX.with_suffix(".tmp.docx")
    shutil.copy2(REFERENCE_DOCX, tmp)
    with ZipFile(tmp, "r") as zin, ZipFile(OUTPUT_DOCX, "w", ZIP_DEFLATED) as zout:
        written = set()
        for item in zin.infolist():
            data = zin.read(item.filename)
            if item.filename == "word/document.xml":
                data = document_xml.encode("utf-8")
            elif item.filename == "word/_rels/document.xml.rels":
                data = rels_xml(data.decode("utf-8"), image_refs).encode("utf-8")
            elif item.filename == "[Content_Types].xml":
                data = content_types_xml(data.decode("utf-8")).encode("utf-8")
            elif item.filename == "word/header1.xml":
                data = header_xml(data.decode("utf-8")).encode("utf-8")
            if item.filename in written:
                continue
            zout.writestr(item, data)
            written.add(item.filename)
        for _, img_path, target_name in image_refs:
            target = f"word/media/{target_name}"
            if target not in written:
                zout.write(img_path, target)
                written.add(target)
    tmp.unlink(missing_ok=True)
    print(OUTPUT_DOCX)


if __name__ == "__main__":
    main()
