"""Compare a source DOCX with its translation and report which Word
structures survived the round trip. It counts structure, not translation
quality: a file can pass every row here and still read badly.

Standard library only. Usage:
    python3 check_docx_roundtrip.py source.docx translated.docx [glossary.csv]

The optional glossary is a two-column CSV (source term, target term), the same
layout DeepL accepts for a one-language-pair glossary upload. For each target
term the script compares how often the source term appears in the source file
with how often the target term appears in the translation (body, footnotes,
header and footer). A lower count points to a place to inspect: a synonym, a
dropped footnote, or a term that went missing. German compounds can contain a
term, so treat the numbers as a pointer, not a verdict.
"""
import csv
import re
import sys
import zipfile

NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"


def read(z, name):
    try:
        return z.read(name).decode("utf-8")
    except KeyError:
        return ""


def text_of(xml):
    return " ".join(re.findall(r"<w:t(?: [^>]*)?>([^<]*)</w:t>", xml))


def parts(z, prefix):
    return "".join(read(z, n) for n in z.namelist() if n.startswith(prefix) and n.endswith(".xml"))


def profile(path):
    with zipfile.ZipFile(path) as z:
        doc = read(z, "word/document.xml")
        rels = read(z, "word/_rels/document.xml.rels")
        styles = read(z, "word/styles.xml")
        footnotes = read(z, "word/footnotes.xml")
        comments = read(z, "word/comments.xml")
        header = parts(z, "word/header")
        footer = parts(z, "word/footer")
    heading_ids = set(re.findall(r'w:styleId="([^"]+)"[^>]*>\s*<w:name w:val="heading \d"', styles))
    used = re.findall(r'<w:pStyle w:val="([^"]+)"', doc)
    return {
        "paragraphs": len(re.findall(r"<w:p[ >]", doc)),
        "heading paragraphs": sum(1 for s in used if s in heading_ids or s.lower().startswith("heading")),
        "tables": doc.count("<w:tbl>"),
        "table cells": doc.count("<w:tc>"),
        "bold runs": len(re.findall(r"<w:b/>|<w:b w:val=\"(?:1|true)\"/>", doc)),
        "hyperlinks": doc.count("<w:hyperlink"),
        "external link targets": len(re.findall(r'TargetMode="External"', rels)),
        "footnote references": doc.count("<w:footnoteReference"),
        "footnote texts": len(re.findall(r'<w:footnote w:id="[1-9]', footnotes)),
        "comments": comments.count("<w:comment "),
        "header text present": int(bool(text_of(header).strip())),
        "footer text present": int(bool(text_of(footer).strip())),
        "_text": " ".join([text_of(doc), text_of(footnotes), text_of(header), text_of(footer)]),
    }


def main():
    if len(sys.argv) < 3:
        sys.exit(__doc__)
    src, out = profile(sys.argv[1]), profile(sys.argv[2])
    failures = 0
    print(f"{'structure':26} {'source':>7} {'output':>7}  result")
    for key in src:
        if key.startswith("_"):
            continue
        ok = out[key] == src[key]
        failures += not ok
        print(f"{key:26} {src[key]:>7} {out[key]:>7}  {'same' if ok else 'CHANGED'}")
    if len(sys.argv) > 3:
        print("\nglossary terms: source count -> target count in output")
        with open(sys.argv[3], newline="", encoding="utf-8") as f:
            for row in csv.reader(f):
                if len(row) < 2:
                    continue
                source_term, target_term = row[0].strip(), row[1].strip()
                expected = len(re.findall(r"\b" + re.escape(source_term) + r"\b", src["_text"], flags=re.IGNORECASE))
                hits = len(re.findall(re.escape(target_term), out["_text"], flags=re.IGNORECASE))
                left = len(re.findall(r"\b" + re.escape(source_term) + r"\b", out["_text"], flags=re.IGNORECASE))
                flag = "check" if hits < expected or left else "ok"
                print(f"  {source_term} -> {target_term}: {expected} -> {hits}, untranslated {left}  {flag}")
    print(f"\n{failures} structure row(s) changed")
    sys.exit(1 if failures else 0)


if __name__ == "__main__":
    main()
