#!/usr/bin/env python3
"""Extract slide text and lightweight structure from a PPTX file."""

from __future__ import annotations

import argparse
import json
import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path

from pptx import Presentation
from pptx.enum.shapes import MSO_SHAPE_TYPE


NS = {"a": "http://schemas.openxmlformats.org/drawingml/2006/main"}


def clean_text(value: str) -> str:
    return " ".join(value.split())


def extract_text_from_xml(xml_bytes: bytes) -> list[str]:
    root = ET.fromstring(xml_bytes)
    texts = []
    for node in root.findall(".//a:t", NS):
        text = clean_text(node.text or "")
        if text:
            texts.append(text)
    return texts


def extract_notes(path: Path) -> dict[int, list[str]]:
    notes: dict[int, list[str]] = {}
    with zipfile.ZipFile(path) as archive:
        for name in archive.namelist():
            if not name.startswith("ppt/notesSlides/notesSlide") or not name.endswith(".xml"):
                continue
            stem = Path(name).stem.replace("notesSlide", "")
            if not stem.isdigit():
                continue
            texts = extract_text_from_xml(archive.read(name))
            if texts:
                notes[int(stem)] = texts
    return notes


def shape_text(shape) -> list[str]:
    chunks: list[str] = []
    if getattr(shape, "has_text_frame", False) and shape.text_frame is not None:
        text = clean_text(shape.text_frame.text)
        if text:
            chunks.append(text)
    if getattr(shape, "has_table", False):
        for row in shape.table.rows:
            values = []
            for cell in row.cells:
                value = clean_text(cell.text)
                if value:
                    values.append(value)
            if values:
                chunks.append(" | ".join(values))
    if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
        for child in shape.shapes:
            chunks.extend(shape_text(child))
    return chunks


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("pptx")
    parser.add_argument("--json-out", required=True)
    parser.add_argument("--md-out", required=True)
    args = parser.parse_args()

    source = Path(args.pptx)
    prs = Presentation(str(source))
    notes = extract_notes(source)
    slides = []

    for idx, slide in enumerate(prs.slides, start=1):
        texts: list[str] = []
        image_count = 0
        table_count = 0
        chart_count = 0

        for shape in slide.shapes:
            if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
                image_count += 1
            if getattr(shape, "has_table", False):
                table_count += 1
            if getattr(shape, "has_chart", False):
                chart_count += 1
            texts.extend(shape_text(shape))

        title = ""
        if slide.shapes.title is not None:
            title = clean_text(slide.shapes.title.text)
        if not title and texts:
            title = texts[0][:80]

        slides.append(
            {
                "slide": idx,
                "title": title,
                "texts": texts,
                "notes": notes.get(idx, []),
                "image_count": image_count,
                "table_count": table_count,
                "chart_count": chart_count,
            }
        )

    data = {
        "source": str(source),
        "slide_count": len(slides),
        "width_emu": prs.slide_width,
        "height_emu": prs.slide_height,
        "slides": slides,
    }

    Path(args.json_out).write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")

    lines = [
        f"# PPTX Text Extract",
        "",
        f"- Source: `{source}`",
        f"- Slides: {len(slides)}",
        f"- Size EMU: {prs.slide_width} x {prs.slide_height}",
        "",
    ]
    for slide in slides:
        lines.append(f"## Slide {slide['slide']}: {slide['title'] or 'Untitled'}")
        lines.append("")
        lines.append(
            f"- Images: {slide['image_count']}; Tables: {slide['table_count']}; Charts: {slide['chart_count']}"
        )
        for text in slide["texts"]:
            lines.append(f"- {text}")
        if slide["notes"]:
            lines.append("- Notes:")
            for note in slide["notes"]:
                lines.append(f"  - {note}")
        lines.append("")

    Path(args.md_out).write_text("\n".join(lines), encoding="utf-8")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
