from __future__ import annotations

import argparse
import json
import re
from pathlib import Path

from lxml import etree


NS = {
    "a": "http://schemas.openxmlformats.org/drawingml/2006/main",
    "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
    "pr": "http://schemas.openxmlformats.org/package/2006/relationships",
    "c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
}


def natural_slide_number(name: str) -> int:
    """函数说明：封装natural_slide_number方法的核心处理流程。"""
    match = re.search(r"(\d+)", name)
    return int(match.group(1)) if match else 0


def read_xml(root: Path, relative_path: str) -> etree._ElementTree:
    """函数说明：封装read_xml方法的核心处理流程。"""
    return etree.parse(str(root / relative_path))


def rel_map(rel_tree: etree._ElementTree) -> dict[str, dict[str, str]]:
    """函数说明：封装rel_map方法的核心处理流程。"""
    mapping: dict[str, dict[str, str]] = {}
    for node in rel_tree.xpath("//pr:Relationship", namespaces=NS):
        mapping[node.get("Id")] = {
            "type": node.get("Type", "").split("/")[-1],
            "target": node.get("Target", ""),
        }
    return mapping


def chart_workbook_target(root: Path, chart_target: str) -> str | None:
    """函数说明：封装chart_workbook_target方法的核心处理流程。"""
    chart_path = Path("ppt/slides") / chart_target
    chart_tree = read_xml(root, str(chart_path))
    external = chart_tree.xpath("//c:externalData", namespaces=NS)
    if not external:
        return None
    rel_id = external[0].get(f"{{{NS['r']}}}id")
    if not rel_id:
        return None
    rel_path = chart_path.parent / "_rels" / f"{chart_path.name}.rels"
    rel_tree = read_xml(root, str(rel_path))
    relationships = rel_map(rel_tree)
    info = relationships.get(rel_id)
    return info["target"] if info else None


def extract_manifest(unpacked_root: Path) -> dict:
    """函数说明：封装extract_manifest方法的核心处理流程。"""
    ppt_root = unpacked_root / "ppt"
    slides_dir = ppt_root / "slides"
    notes_dir = ppt_root / "notesSlides"
    charts_dir = ppt_root / "charts"
    embeddings_dir = ppt_root / "embeddings"
    media_dir = ppt_root / "media"

    slides: list[dict] = []
    slide_files = sorted(slides_dir.glob("slide*.xml"), key=lambda p: natural_slide_number(p.name))
    for slide_file in slide_files:
        slide_no = natural_slide_number(slide_file.name)
        slide_tree = etree.parse(str(slide_file))
        rel_file = slides_dir / "_rels" / f"{slide_file.name}.rels"
        relationships = rel_map(etree.parse(str(rel_file))) if rel_file.exists() else {}

        texts = [text for text in slide_tree.xpath("//a:t/text()", namespaces=NS)]
        note_target = None
        chart_targets: list[str] = []
        image_targets: list[str] = []
        other_targets: list[dict[str, str]] = []

        for rel_id, info in relationships.items():
            rel_type = info["type"]
            target = info["target"]
            if rel_type == "notesSlide":
                note_target = target
            elif rel_type == "chart":
                chart_targets.append(target)
            elif rel_type == "image":
                image_targets.append(target)
            elif rel_type != "slideLayout":
                other_targets.append({"type": rel_type, "target": target, "rel_id": rel_id})

        note_texts: list[str] = []
        if note_target:
            note_path = (Path("ppt/slides") / note_target).resolve().as_posix()
            marker = "/ppt/"
            relative_note = "ppt/" + note_path.split(marker, 1)[1]
            note_tree = read_xml(unpacked_root, relative_note)
            note_texts = [text for text in note_tree.xpath("//a:t/text()", namespaces=NS)]

        charts = []
        for target in chart_targets:
            workbook = chart_workbook_target(unpacked_root, target)
            charts.append(
                {
                    "chart_target": target,
                    "embedded_workbook_target": workbook,
                }
            )

        slides.append(
            {
                "slide_no": slide_no,
                "slide_file": slide_file.name,
                "text_preview": texts[:40],
                "text_count": len(texts),
                "note_text": note_texts,
                "image_targets": image_targets,
                "image_count": len(image_targets),
                "charts": charts,
                "chart_count": len(charts),
                "other_targets": other_targets,
            }
        )

    return {
        "unpacked_root": str(unpacked_root),
        "slide_count": len(slides),
        "notes_slide_count": len(list(notes_dir.glob("notesSlide*.xml"))),
        "chart_file_count": len(list(charts_dir.glob("chart*.xml"))),
        "embedding_file_count": len(list(embeddings_dir.glob("Workbook*.xlsx"))),
        "media_file_count": len(list(media_dir.glob("*"))),
        "slides": slides,
    }


def main() -> None:
    """函数说明：脚本主入口，负责串联参数解析与执行流程。"""
    parser = argparse.ArgumentParser(description="Extract a machine-readable manifest from an unpacked PPTX.")
    parser.add_argument("unpacked_root", help="Root directory of the unpacked pptx package")
    parser.add_argument("--output", help="Optional output json path. Prints to stdout when omitted.")
    # 关键行注释：解析命令行参数，生成本次运行配置。
    args = parser.parse_args()

    manifest = extract_manifest(Path(args.unpacked_root))
    payload = json.dumps(manifest, ensure_ascii=False, indent=2)
    if args.output:
        output_path = Path(args.output)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(payload, encoding="utf-8")
    else:
        print(payload)


# 关键行注释：脚本直接运行时，从这里进入主流程。
if __name__ == "__main__":
    main()
