#!/usr/bin/env python3
"""Create a synthetic DOCX and compare characters read back from its XML.

Usage: python3 /path/to/docx-character-matching.py [output.docx]
The default output is beside this script, regardless of the working directory.
Requires Python 3.10+ and only the standard library. Existing files are never
overwritten; an identical fixture is verified without rewriting it.

This reproduces underlying DOCX character differences, NOT memoQ matching or
font fallback runtime. It is not a general-purpose Word text extractor: the
fixture stores literal characters in w:t, not w:noBreakHyphen or other elements.
Normalization here is a diagnostic comparison, not a recommendation to change
source typography. All text is synthetic; no AI provenance is implied.
"""

import argparse
from io import BytesIO
from pathlib import Path
import unicodedata
import xml.etree.ElementTree as ET
from zipfile import ZIP_STORED, ZipFile, ZipInfo


WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
SAMPLES = {
    "ascii_hyphen": "co-operate",
    "hyphen": "co\u2010operate",
    "nonbreaking_hyphen": "co\u2011operate",
    "space": "10 kg",
    "no_break_space": "10\u00a0kg",
    "narrow_no_break_space": "10\u202fkg",
    "composed_accent": "caf\u00e9",
    "decomposed_accent": "cafe\u0301",
}


def fixture_bytes():
    """Build the three required package parts with fixed ZIP metadata."""
    ET.register_namespace("w", WORD_NS)
    document = ET.Element(f"{{{WORD_NS}}}document")
    body = ET.SubElement(document, f"{{{WORD_NS}}}body")
    lines = [
        "Synthetic DOCX character comparison",
        "Similar-looking hyphens and spaces can contain different code points.",
        "This fixture demonstrates XML storage and Unicode normalization only.",
        *[f"{label}: {value}" for label, value in SAMPLES.items()],
    ]
    for line in lines:
        paragraph = ET.SubElement(body, f"{{{WORD_NS}}}p")
        run = ET.SubElement(paragraph, f"{{{WORD_NS}}}r")
        ET.SubElement(run, f"{{{WORD_NS}}}t").text = line
    parts = {
        "[Content_Types].xml": (
            '<?xml version="1.0" encoding="UTF-8"?>'
            '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
            '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
            '<Default Extension="xml" ContentType="application/xml"/>'
            '<Override PartName="/word/document.xml" '
            'ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
            '</Types>'
        ).encode("utf-8"),
        "_rels/.rels": (
            '<?xml version="1.0" encoding="UTF-8"?>'
            '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
            '<Relationship Id="rId1" '
            'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" '
            'Target="word/document.xml"/>'
            '</Relationships>'
        ).encode("utf-8"),
        "word/document.xml": ET.tostring(document, encoding="utf-8", xml_declaration=True),
    }
    buffer = BytesIO()
    with ZipFile(buffer, "w", compression=ZIP_STORED) as archive:
        for name, data in parts.items():
            info = ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
            info.create_system = 3
            info.external_attr = 0o100644 << 16
            archive.writestr(info, data)
    return buffer.getvalue()


def read_samples(path):
    """Read the generated fixture, preserving its literal Unicode characters."""
    with ZipFile(path) as archive:
        assert archive.testzip() is None, "ZIP checksum validation failed"
        document = ET.fromstring(archive.read("word/document.xml"))
    samples = {}
    for paragraph in document.findall(f"{{{WORD_NS}}}body/{{{WORD_NS}}}p"):
        text = "".join(node.text or "" for node in paragraph.iter(f"{{{WORD_NS}}}t"))
        label, separator, value = text.partition(": ")
        if separator and label in SAMPLES:
            assert label not in samples, f"Duplicate sample: {label}"
            samples[label] = value
    assert samples == SAMPLES, "DOCX text changed during the XML round trip"
    return samples


def demonstrate(samples):
    for label, value in samples.items():
        points = " ".join(f"U+{ord(char):04X}" for char in value)
        print(f"{label}: {ascii(value)} | {points}")
    comparisons = [
        ("ascii_hyphen", "nonbreaking_hyphen", (False, False, False)),
        ("hyphen", "nonbreaking_hyphen", (False, False, True)),
        ("space", "no_break_space", (False, False, True)),
        ("space", "narrow_no_break_space", (False, False, True)),
        ("composed_accent", "decomposed_accent", (False, True, True)),
    ]
    for left, right, expected in comparisons:
        a, b = samples[left], samples[right]
        actual = (
            a == b,
            unicodedata.normalize("NFC", a) == unicodedata.normalize("NFC", b),
            unicodedata.normalize("NFKC", a) == unicodedata.normalize("NFKC", b),
        )
        assert actual == expected, f"Unexpected normalization result: {left} vs {right}"
        exact, nfc, nfkc = actual
        print(f"{left} vs {right}: exact={exact} NFC={nfc} NFKC={nfkc}")
    assert unicodedata.normalize("NFKC", "\u2011") == "\u2010"
    assert unicodedata.normalize("NFKC", "\u2010") != "-"
    assert unicodedata.normalize("NFKC", "\u202f") == " "
    print("All XML round-trip and normalization assertions passed.")
    print("Reproduces underlying DOCX character differences, NOT memoQ matching or font fallback runtime.")


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("output", nargs="?", type=Path, help="output DOCX (default: beside this script)")
    args = parser.parse_args()
    output = args.output if args.output is not None else Path(__file__).resolve().with_suffix(".docx")
    data = fixture_bytes()
    try:
        if output.is_symlink():
            parser.error(f"Refusing symlink output: {output}")
        if output.exists():
            if not output.is_file() or output.read_bytes() != data:
                parser.error(f"Refusing to overwrite existing non-fixture output: {output}")
            action = "Verified existing fixture"
        else:
            # Exclusive creation also prevents overwriting a file created meanwhile.
            with output.open("xb") as stream:
                stream.write(data)
            action = "Created fixture"
        samples = read_samples(output)
    except (OSError, ValueError) as error:
        parser.error(str(error))
    print(f"{action}: {output}")
    print(f"Unicode database: {unicodedata.unidata_version}")
    demonstrate(samples)


if __name__ == "__main__":
    main()
