"""Read-only DOCX review inventory. Python 3, standard library only.

Does not upload, extract, alter, accept, or reject a document. Counts XML
elements, not Word UI revision groups. Conventional DOCX only; encrypted
Office files are unsupported. Comments in modern extension parts are flagged.
"""
import argparse
import json
import zipfile
from pathlib import Path
from xml.etree import ElementTree as ET

W = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
MAX_XML = 16 * 1024 * 1024
MAX_TOTAL_XML = 64 * 1024 * 1024


def inspect(path):
    counts = {'insertions': 0, 'deletions': 0, 'moves_from': 0,
              'moves_to': 0, 'format_changes': 0, 'comments': 0,
              'comment_references': 0, 'tables': 0}
    scanned = []
    with zipfile.ZipFile(path) as package:
        names = package.namelist()
        if len(names) != len(set(names)):
            raise ValueError('Duplicate ZIP entry names are unsupported')
        if 'word/document.xml' not in names:
            raise ValueError('No conventional Word document part found')
        selected = [n for n in names if n.startswith('word/') and n.endswith('.xml')]
        infos = [package.getinfo(n) for n in selected]
        if sum(i.file_size for i in infos) > MAX_TOTAL_XML:
            raise ValueError('XML exceeds the 64 MiB inspection limit')
        for info in infos:
            if info.file_size > MAX_XML:
                raise ValueError('One XML part exceeds the 16 MiB inspection limit')
            root = ET.fromstring(package.read(info))
            scanned.append(info.filename)
            for node in root.iter():
                if not node.tag.startswith(W):
                    continue
                tag = node.tag[len(W):]
                mapping = {'ins': 'insertions', 'del': 'deletions',
                           'moveFrom': 'moves_from', 'moveTo': 'moves_to',
                           'comment': 'comments', 'commentReference': 'comment_references',
                           'tbl': 'tables'}
                if tag in mapping:
                    counts[mapping[tag]] += 1
                if tag.endswith('PrChange') or tag == 'tblGridChange':
                    counts['format_changes'] += 1
        extension_parts = [n for n in names if n.startswith('word/') and
                           any(v in n.lower() for v in ('commentsextended', 'commentsextensible', 'commentsids'))]
    return {'file': Path(path).name, 'counts': counts, 'scanned_parts': scanned,
            'modern_comment_parts': extension_parts,
            'meaning': 'Element inventory only. Does not prove conversion fidelity, complete review semantics, or layout.'}


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('files', nargs='+', help='DOCX copies to inventory')
    args = parser.parse_args()
    try:
        print(json.dumps([inspect(path) for path in args.files], indent=2))
    except (OSError, ValueError, ET.ParseError, zipfile.BadZipFile) as error:
        parser.exit(2, f'Cannot inspect: {error}\n')
