#!/usr/bin/env python3
"""Unified PPTX preparation entry point for the /create-template workflow.

Reads OOXML directly via `pptx_to_svg` and writes a reusable reference workspace:

- `manifest.json` — single source of truth for slide size, theme colors, fonts,
  asset inventory, and per-slide / per-layout / per-master metadata
- `native_structure.json` + `source_template.pptx` — source-structure facts and
  a byte-identical analysis copy used to rebuild explicit SVG structure
- `assets/` — extracted reusable image assets
- `conversion-report.json` — source-recovery and fidelity diagnostics emitted
  with SVG conversion
- `svg/` — canonical layered template view (every master
  and layout in the deck rendered once each as `master_*.svg` /
  `layout_*.svg`, slides contain only their own shapes, and an
  `inheritance.json` describes the reuse graph)
- `svg-flat/` — optional verification view (`--inheritance-mode both`): each
  `slide_NN.svg` is self-contained, so opening one slide shows the full page
  like PowerPoint would
"""

from __future__ import annotations

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

from console_encoding import configure_utf8_stdio
from template_import.manifest import build_manifest
from template_import.native_structure import (
    CONTRACT_NAME,
    SOURCE_TEMPLATE_NAME,
    write_native_structure_bundle,
)

configure_utf8_stdio()

_MANIFEST_NAME = "manifest.json"
_CONVERSION_REPORT_NAME = "conversion-report.json"


def parse_args() -> argparse.Namespace:
    """Build the CLI argument parser for the import entry point."""
    parser = argparse.ArgumentParser(
        description="Prepare a PPTX reference workspace for /create-template."
    )
    parser.add_argument("pptx_file", help="Path to the source .pptx file")
    parser.add_argument(
        "-o",
        "--output",
        help="Output directory (default: <pptx_stem>_template_import beside the source file)",
    )
    parser.add_argument(
        "--skip-manifest",
        action="store_true",
        help=(
            "Skip metadata, asset inventory, native structure contract, and "
            "preserved source-package generation"
        ),
    )
    parser.add_argument(
        "--manifest-only",
        action="store_true",
        help=(
            "Only extract manifest.json + reusable assets + the native "
            "structure/source pair, without exporting slides to SVG"
        ),
    )
    parser.add_argument(
        "--embed-images",
        action="store_true",
        help="Inline images as data: URIs instead of writing files to assets/",
    )
    parser.add_argument(
        "--inheritance-mode",
        choices=("both", "layered", "flat"),
        default="layered",
        help=(
            "How to render master/layout shapes for slide SVGs. "
            "'layered' (default): emit the canonical svg/ tree with master, "
            "layout, and slide-local files plus svg/inheritance.json. "
            "'both': also emit svg-flat/ with self-contained per-slide "
            "verification files. In this mode svg/ still holds the layered "
            "renderings (template designers see master/layout/slide as "
            "separate files). 'flat': emit only self-contained slide SVGs "
            "in svg/, the round-trip view used by svg_to_pptx."
        ),
    )
    return parser.parse_args()


def _managed_asset_paths(output_dir: Path) -> set[Path]:
    """Read the previous manifest's exact exported-asset roster."""
    manifest_path = output_dir / _MANIFEST_NAME
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except (OSError, UnicodeError, json.JSONDecodeError):
        return set()
    if not isinstance(manifest, dict):
        return set()
    assets = manifest.get("assets")
    if not isinstance(assets, dict):
        return set()
    export_dir = assets.get("exportDir")
    asset_names = assets.get("allAssets")
    if export_dir != "assets" or not isinstance(asset_names, list):
        return set()
    if any(
        not isinstance(name, str)
        or not name
        or name in {".", ".."}
        or "/" in name
        or "\\" in name
        for name in asset_names
    ):
        return set()
    return {
        Path("assets") / name
        for name in asset_names
    }


def main() -> int:
    """CLI entry point: write the PPTX reference workspace to disk."""
    args = parse_args()
    pptx_path = Path(args.pptx_file).expanduser().resolve()
    if not pptx_path.exists():
        print(f"Error: file does not exist: {pptx_path}")
        return 1
    if pptx_path.suffix.lower() != ".pptx":
        print(f"Error: expected a .pptx file, got: {pptx_path.name}")
        return 1

    output_dir = (
        Path(args.output).expanduser().resolve()
        if args.output
        else pptx_path.with_name(f"{pptx_path.stem}_template_import")
    )

    if args.skip_manifest and args.manifest_only:
        print("Error: --skip-manifest and --manifest-only cannot be used together")
        return 1

    previous_assets = _managed_asset_paths(output_dir)
    output_dir.parent.mkdir(parents=True, exist_ok=True)
    staging_root = Path(tempfile.mkdtemp(
        prefix=f".{output_dir.name}.import-",
        dir=output_dir.parent,
    ))
    staged_dir = staging_root / "generated"
    staged_dir.mkdir()

    try:
        manifest = None
        native_structure = None
        manifest_path = staged_dir / _MANIFEST_NAME
        if not args.skip_manifest:
            try:
                manifest = build_manifest(
                    pptx_path,
                    staged_dir,
                    include_flat_svg=(
                        not args.manifest_only and args.inheritance_mode == "both"
                    ),
                )
            except (RuntimeError, OSError, ValueError) as exc:
                print(f"Error: failed to extract PPTX metadata: {exc}")
                return 1

            manifest_path.write_text(
                json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
                encoding="utf-8",
            )
            try:
                native_structure = write_native_structure_bundle(
                    pptx_path,
                    staged_dir,
                    manifest,
                )
            except (OSError, ValueError) as exc:
                print(f"Error: failed to write native structure bundle: {exc}")
                return 1

        result = None
        total_bytes = 0
        if not args.manifest_only:
            from pptx_to_svg import convert_pptx_to_svg
            from pptx_to_svg.converter import ConvertOptions

            options = ConvertOptions(
                media_subdir="assets",
                embed_images=args.embed_images,
                keep_hidden=False,
                inheritance_mode=args.inheritance_mode,
                asset_name_map=(
                    manifest.get("assets", {}).get("assetMap", {})
                    if manifest else {}
                ),
            )
            try:
                result = convert_pptx_to_svg(pptx_path, staged_dir, options)
            except (BadZipFile, ET.ParseError, OSError, RuntimeError, ValueError) as exc:
                print(f"Error: failed to convert PPTX template source: {exc}")
                return 1
            total_bytes = sum(
                len(art.svg.encode("utf-8"))
                for art in result.slides
            )

        from pptx_to_svg.converter import publish_staged_workspace

        try:
            publish_staged_workspace(
                output_dir,
                staged_dir,
                managed_root_files={
                    _MANIFEST_NAME,
                    CONTRACT_NAME,
                    SOURCE_TEMPLATE_NAME,
                    _CONVERSION_REPORT_NAME,
                },
                managed_relative_paths=previous_assets,
            )
        except (OSError, RuntimeError, ValueError) as exc:
            print(f"Error: failed to publish PPTX template workspace: {exc}")
            return 1

        if args.manifest_only:
            print(f"Imported PPTX template source: {pptx_path.name}")
            print(f"Output directory: {output_dir}")
            if manifest is not None:
                print(f"Manifest: {manifest_path.name}")
                print(f"Native structure: {CONTRACT_NAME}")
                print(f"Source package analysis copy: {SOURCE_TEMPLATE_NAME}")
                print(
                    "Source structure assessment: "
                    f"{native_structure['strategy']['recommendedMode']}"
                )
                print("Template output mode: explicit SVG structure")
                print(f"Assets exported: {len(manifest['assets']['allAssets'])}")
                print(f"Common assets: {len(manifest['assets']['commonAssets'])}")
                print(f"Slides analyzed: {len(manifest['slides'])}")
                print(f"Layouts (unique): {len(manifest.get('layouts', []))}")
                print(f"Masters (unique): {len(manifest.get('masters', []))}")
            return 0

        print(f"Inheritance mode: {args.inheritance_mode}")
        print(f"Exported SVG slides: {len(result.slides)}")
        if args.inheritance_mode in {"layered", "both"}:
            print(f"Exported masters: {len(result.masters)}")
            print(f"Exported layouts: {len(result.layouts)}")
            print("Inheritance graph: svg/inheritance.json")
        if result.flat_slides:
            print(f"Flat companion slides: {len(result.flat_slides)} (svg-flat/)")
        if result.diagnostics:
            print(
                f"Source recovery warnings: {len(result.diagnostics)} "
                f"({_CONVERSION_REPORT_NAME})"
            )
        print(f"SVG bytes (primary): {total_bytes}")
        print(f"Output directory: {output_dir}")
        if native_structure is not None:
            print(
                "Source structure assessment: "
                f"{native_structure['strategy']['recommendedMode']}; "
                "create-template rebuilds explicit SVG structure"
            )
        return 0
    finally:
        shutil.rmtree(staging_root, ignore_errors=True)


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