#!/usr/bin/env python3
"""
PPT Master - Mirror Template Materializer

Materialize a deterministic structured SVG template workspace from one Type A
PPTX import workspace. The editable authoring IR is the only authoring input;
lossless SVG files are consulted solely to restore unchanged supported source
objects. Final templates and imported vectors contain no IR-only source refs.

Usage:
    python3 scripts/mirror_template_materialize.py \
        <import_workspace> <template_workspace>

Dependencies:
    None (standard library and sibling PPT Master modules only).
"""

from __future__ import annotations

import argparse
import base64
import copy
import hashlib
import json
import math
import os
import re
import sys
import tempfile
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable
from urllib.parse import unquote, urlsplit, urlunsplit
from xml.etree import ElementTree as ET

from compact_svg_coordinates import compact_svg_tree, format_coordinate
from console_encoding import configure_utf8_stdio
from native_payloads import (
    PAYLOAD_STORE_RELATIVE_PATH,
    NativePayloadError,
    NativePayloadStats,
    build_native_attribute_records,
    collect_native_attribute_record_keys,
    externalize_native_attribute_records,
    externalize_native_payloads,
    hydrate_native_payload_refs,
    serialize_native_payload_store,
)
from pptx_shapes import svg_preset_preview_fingerprint
from svg_authoring_view import (
    AUTHORING_MANIFEST_NAME,
    AUTHORING_SCHEMA,
    SOURCE_REF_ATTRIBUTE,
    semantic_subtree_sha256,
)
from svg_finalize.flatten_tspan import flatten_text_with_tspans
from svg_to_pptx.pptx_package.template_structure import (
    TemplateStructureError,
    parse_template_slides,
)
from template_text_slots import (
    analyze_template_text_slots,
    text_slot_integrity_sha256,
)

configure_utf8_stdio()

SVG_NS = "http://www.w3.org/2000/svg"
XLINK_NS = "http://www.w3.org/1999/xlink"
XML_NS = "http://www.w3.org/XML/1998/namespace"
NATIVE_STRUCTURE_SCHEMA = "ppt-master.native-structure.v1"
VECTOR_INVENTORY_SCHEMA = "vector_asset_inventory.v1"
TEMPLATE_EXECUTION_MANIFEST_NAME = "template_execution_manifest.json"
TEMPLATE_EXECUTION_MANIFEST_SCHEMA = "ppt-master.template-execution-manifest.v1"
TEMPLATE_TEXT_SLOTS_DIR = "template_execution"
TEMPLATE_TEXT_SLOTS_SCHEMA = "ppt-master.template-text-slots.v2-min"
IMPORTED_ICON_NAMESPACE = "imported"
TRANSPARENT_PIXEL_DATA_URI = (
    "data:image/png;base64,"
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgAAIAAAUA"
    "AXpeqz8AAAAASUVORK5CYII="
)

ET.register_namespace("", SVG_NS)
ET.register_namespace("xlink", XLINK_NS)

_NON_VISUAL_TAGS = frozenset({"defs", "desc", "metadata", "style", "title"})
_BITMAP_EXTENSIONS = frozenset({
    ".avif",
    ".bmp",
    ".gif",
    ".jpeg",
    ".jpg",
    ".png",
    ".tif",
    ".tiff",
    ".webp",
})
_INHERITED_PRESENTATION_ATTRIBUTES = frozenset({
    "color",
    "fill",
    "fill-opacity",
    "font-family",
    "font-size",
    "font-style",
    "font-weight",
    "letter-spacing",
    "paint-order",
    "shape-rendering",
    "stroke",
    "stroke-dasharray",
    "stroke-dashoffset",
    "stroke-linecap",
    "stroke-linejoin",
    "stroke-miterlimit",
    "stroke-opacity",
    "stroke-width",
    "text-anchor",
    "text-decoration",
    "word-spacing",
})
_AGGREGATE_GROUP_ATTRIBUTES = frozenset({
    "clip-path",
    "filter",
    "mask",
    "mix-blend-mode",
    "opacity",
})
_URL_REFERENCE_RE = re.compile(r"url\(\s*(['\"]?)#([^)'\"\s]+)\1\s*\)")
_CSS_ID_RE = re.compile(r"#([A-Za-z_][A-Za-z0-9_.:-]*)")
_SAFE_KEY_RE = re.compile(r"[^A-Za-z0-9_.-]+")
_TRANSFORM_NUMBER = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?"
_AXIS_REFLECTION_RE = re.compile(
    rf"^\s*translate\(\s*({_TRANSFORM_NUMBER})[\s,]+"
    rf"({_TRANSFORM_NUMBER})\s*\)\s*"
    rf"scale\(\s*({_TRANSFORM_NUMBER})[\s,]+"
    rf"({_TRANSFORM_NUMBER})\s*\)\s*"
    rf"translate\(\s*({_TRANSFORM_NUMBER})[\s,]+"
    rf"({_TRANSFORM_NUMBER})\s*\)\s*$"
)


class MirrorMaterializationError(RuntimeError):
    """Reject incomplete, ambiguous, or unsafe mirror materialization input."""


@dataclass(frozen=True)
class SourceRefRecord:
    source_path: tuple[int, ...]
    initial_authoring_subtree_sha256: str


@dataclass(frozen=True)
class AuthoringDocument:
    name: str
    authoring_path: Path
    source_path: Path
    source_sha256: str
    source_refs: dict[str, SourceRefRecord]


@dataclass(frozen=True)
class VectorAssetRecord:
    icon: str
    asset_path: Path
    origin_document: str
    expected_sha256: str
    source_refs: tuple[str, ...]


@dataclass(frozen=True)
class SlotPlan:
    slot_id: str
    semantic_role: str
    placeholder_type: str | None
    idx: int | None
    shape_id: str
    bounds: tuple[float, float, float, float]


@dataclass
class RestorationStats:
    rehydrated_refs: int = 0
    fallback_refs: int = 0
    structural_refs: int = 0
    detached_connector_endpoints: int = 0
    upright_text_compensations: int = 0

    def merge(self, other: "RestorationStats") -> None:
        self.rehydrated_refs += other.rehydrated_refs
        self.fallback_refs += other.fallback_refs
        self.structural_refs += other.structural_refs
        self.detached_connector_endpoints += other.detached_connector_endpoints
        self.upright_text_compensations += other.upright_text_compensations

    def as_dict(self) -> dict[str, int]:
        return {
            "rehydrated_refs": self.rehydrated_refs,
            "fallback_refs": self.fallback_refs,
            "structural_refs": self.structural_refs,
            "detached_connector_endpoints": self.detached_connector_endpoints,
            "upright_text_compensations": self.upright_text_compensations,
        }


@dataclass
class MaterializedFile:
    relative_path: Path
    payload: bytes


def _json_bytes(payload: dict[str, object]) -> bytes:
    return (
        json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
    ).encode("utf-8")


def _compact_json_bytes(payload: dict[str, object]) -> bytes:
    return (
        json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n"
    ).encode("utf-8")


def _template_execution_manifest_files(
    materialized_roots: list[tuple[Path, ET.Element]],
    source_import: dict[str, object] | None,
) -> list[MaterializedFile]:
    """Serialize one compact roster plus per-prototype text-slot sidecars."""
    templates: list[dict[str, object]] = []
    files: list[MaterializedFile] = []
    for relative_path, root in sorted(
        materialized_roots,
        key=lambda item: item[0].as_posix(),
    ):
        prototype = relative_path.name
        try:
            analyzed_slots = analyze_template_text_slots(root)
        except ValueError as exc:
            raise MirrorMaterializationError(
                f"Cannot project text slots for {prototype}: {exc}"
            ) from exc
        text_slots = [slot.model_payload() for slot in analyzed_slots]
        editable_text_slot_count = sum(slot.editable for slot in analyzed_slots)
        text_slots_path = (
            Path("templates")
            / TEMPLATE_TEXT_SLOTS_DIR
            / f"{relative_path.stem}.text-slots.json"
        )
        files.append(MaterializedFile(
            text_slots_path,
            _compact_json_bytes({
                "schema": TEMPLATE_TEXT_SLOTS_SCHEMA,
                "prototype": prototype,
                "text_slot_count": len(text_slots),
                "tool_integrity_sha256": text_slot_integrity_sha256(analyzed_slots),
                "text_slots": text_slots,
            }),
        ))
        templates.append({
            "prototype": prototype,
            "page_type": relative_path.stem.split("_", 1)[-1],
            "viewBox": root.get("viewBox"),
            "master": root.get("data-pptx-master"),
            "layout": root.get("data-pptx-layout"),
            "layout_name": root.get("data-pptx-layout-name"),
            "text_slot_count": len(text_slots),
            "editable_text_slot_count": editable_text_slot_count,
            "text_slots_path": text_slots_path.relative_to(
                Path("templates")
            ).as_posix(),
        })
    payload = {
        "schema": TEMPLATE_EXECUTION_MANIFEST_SCHEMA,
        "replication_mode": "mirror",
        "template_root": ".",
        "template_count": len(templates),
        "text_slots_schema": TEMPLATE_TEXT_SLOTS_SCHEMA,
        "execution_policy": (
            "This manifest and its text_slots_path records are derived tool "
            "metadata, not page-authoring inputs. Retain the complete project "
            "Design Spec and lock once per valid uncompacted execution context; "
            "use page-context only for on-demand diagnostics or telemetry. "
            "Read the selected complete prototype once per valid context and "
            "again only after a known change or context invalidation. Choose "
            "semantic replacements and edit only existing "
            "visible text values; structured export validates text/tspan "
            "topology and attributes against the prototype."
        ),
        "source_import": source_import or {
            "warning_count": 0,
            "by_code": {},
        },
        "templates": templates,
    }
    files.append(MaterializedFile(
        Path("templates") / TEMPLATE_EXECUTION_MANIFEST_NAME,
        _json_bytes(payload),
    ))
    return files


def _source_import_summary(import_workspace: Path) -> dict[str, object] | None:
    """Summarize source-owned tolerant-import diagnostics by stable code."""
    report_path = import_workspace / "conversion-report.json"
    try:
        report = json.loads(report_path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return None
    except (OSError, json.JSONDecodeError) as exc:
        raise MirrorMaterializationError(
            f"Cannot read source conversion report {report_path}: {exc}"
        ) from exc
    diagnostics = report.get("diagnostics")
    if not isinstance(diagnostics, list):
        raise MirrorMaterializationError(
            f"Source conversion report has no diagnostics array: {report_path}"
        )
    by_code: Counter[str] = Counter()
    samples: dict[str, str] = {}
    for item in diagnostics:
        if not isinstance(item, dict):
            continue
        severity = str(item.get("severity") or "warning").lower()
        if severity != "warning":
            continue
        code = str(item.get("code") or "unknown")
        by_code[code] += 1
        message = item.get("message")
        if code not in samples and isinstance(message, str) and message:
            samples[code] = message
    return {
        "warning_count": sum(by_code.values()),
        "by_code": dict(sorted(by_code.items())),
        "samples": dict(sorted(samples.items())),
    }


def _local_name(name: object) -> str:
    return name.rsplit("}", 1)[-1] if isinstance(name, str) else ""


def _axis_reflection_transform(value: str | None) -> str | None:
    """Return one exact importer axis-reflection transform when valid."""
    if not value:
        return None
    match = _AXIS_REFLECTION_RE.fullmatch(value)
    if match is None:
        return None
    cx, cy, scale_x, scale_y, offset_x, offset_y = (
        float(token) for token in match.groups()
    )
    if not (
        math.isclose(abs(scale_x), 1.0, abs_tol=1e-9)
        and math.isclose(abs(scale_y), 1.0, abs_tol=1e-9)
        and (scale_x < 0 or scale_y < 0)
        and math.isclose(offset_x, -cx, abs_tol=1e-7)
        and math.isclose(offset_y, -cy, abs_tol=1e-7)
    ):
        return None
    return value.strip()


def _compensate_reflected_group_text(root: ET.Element) -> int:
    """Keep browser-visible text upright inside imported flipped groups."""
    parent_by_child = {
        child: parent
        for parent in root.iter()
        for child in parent
    }
    reflected_groups: list[tuple[int, ET.Element, str]] = []
    for element in root.iter():
        if _local_name(element.tag) != "g":
            continue
        transform = _axis_reflection_transform(element.get("transform"))
        if transform is None:
            continue
        depth = 0
        current = element
        while current in parent_by_child:
            depth += 1
            current = parent_by_child[current]
        reflected_groups.append((depth, element, transform))

    wrapped = 0
    for _depth, group, transform in sorted(
        reflected_groups,
        key=lambda item: item[0],
        reverse=True,
    ):
        text_elements = [
            element
            for element in group.iter()
            if _local_name(element.tag) == "text"
        ]
        for text in text_elements:
            current_parents = {
                child: parent
                for parent in group.iter()
                for child in parent
            }
            parent = current_parents.get(text)
            if parent is None:
                continue
            position = list(parent).index(text)
            parent.remove(text)
            wrapper = ET.Element(
                f"{{{SVG_NS}}}g",
                {"transform": transform},
            )
            wrapper.append(text)
            parent.insert(position, wrapper)
            wrapped += 1
    return wrapped


def _sha256_bytes(payload: bytes) -> str:
    return hashlib.sha256(payload).hexdigest()


def _sha256_file(path: Path) -> str:
    return _sha256_bytes(path.read_bytes())


def _load_json(path: Path, *, context: str) -> dict[str, Any]:
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except OSError as exc:
        raise MirrorMaterializationError(f"Cannot read {context}: {path}: {exc}") from exc
    except json.JSONDecodeError as exc:
        raise MirrorMaterializationError(
            f"Invalid JSON in {context}: {path}: {exc}"
        ) from exc
    if not isinstance(payload, dict):
        raise MirrorMaterializationError(f"{context} must be a JSON object: {path}")
    return payload


def _require_list(value: object, *, context: str) -> list[Any]:
    if not isinstance(value, list):
        raise MirrorMaterializationError(f"{context} must be a list")
    return value


def _require_string(value: object, *, context: str) -> str:
    if not isinstance(value, str) or not value.strip():
        raise MirrorMaterializationError(f"{context} must be a non-empty string")
    return value.strip()


def _require_boolean(value: object, *, context: str) -> bool:
    if not isinstance(value, bool):
        raise MirrorMaterializationError(f"{context} must be a boolean")
    return value


def _resolve_inside(root: Path, relative: str, *, context: str) -> Path:
    parsed = urlsplit(relative)
    if parsed.scheme or parsed.netloc or Path(parsed.path).is_absolute():
        raise MirrorMaterializationError(f"{context} must be a relative path: {relative}")
    resolved = (root / unquote(parsed.path)).resolve()
    try:
        resolved.relative_to(root.resolve())
    except ValueError as exc:
        raise MirrorMaterializationError(
            f"{context} escapes its declared root: {relative}"
        ) from exc
    return resolved


def _parse_svg(path: Path) -> ET.Element:
    parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True, insert_pis=True))
    try:
        root = ET.fromstring(path.read_bytes(), parser=parser)
    except (OSError, ET.ParseError) as exc:
        raise MirrorMaterializationError(f"Cannot parse SVG {path}: {exc}") from exc
    if _local_name(root.tag) != "svg":
        raise MirrorMaterializationError(f"SVG root is not <svg>: {path}")
    return root


def _source_element(root: ET.Element, path: tuple[int, ...]) -> ET.Element:
    element = root
    try:
        for index in path:
            element = list(element)[index]
    except (IndexError, TypeError) as exc:
        raise MirrorMaterializationError(
            f"Source-ref path no longer resolves: {list(path)}"
        ) from exc
    return element


def _source_identity(element: ET.Element) -> str | None:
    scope = element.get("data-pptx-shape-scope")
    shape_id = element.get("data-pptx-shape-id")
    if not scope or not shape_id:
        return None
    return f"{scope}:{shape_id}"


def _load_authoring_documents(
    workspace: Path,
) -> tuple[Path, dict[str, AuthoringDocument]]:
    authoring_root = workspace / "authoring-svg"
    manifest_path = authoring_root / AUTHORING_MANIFEST_NAME
    manifest = _load_json(manifest_path, context="authoring manifest")
    if manifest.get("schema") != AUTHORING_SCHEMA:
        raise MirrorMaterializationError(
            f"Unsupported authoring manifest schema: {manifest.get('schema')!r}"
        )
    if manifest.get("projection_kind") != "layered":
        raise MirrorMaterializationError(
            "Mirror materialization requires projection_kind='layered'"
        )
    if manifest.get("authoring_root") != ".":
        raise MirrorMaterializationError("authoring_manifest.json authoring_root must be '.'")
    if manifest.get("source_ref_attribute") != SOURCE_REF_ATTRIBUTE:
        raise MirrorMaterializationError(
            "authoring_manifest.json uses an unsupported source-ref attribute"
        )

    source_root_raw = _require_string(
        manifest.get("source_root"),
        context="authoring_manifest.json source_root",
    )
    source_root = (authoring_root / source_root_raw).resolve()
    expected_source_root = (workspace / "svg").resolve()
    if source_root != expected_source_root:
        raise MirrorMaterializationError(
            "Type A mirror authoring manifest must resolve source_root to "
            f"{expected_source_root}, found {source_root}"
        )

    documents_raw = _require_list(
        manifest.get("documents"),
        context="authoring_manifest.json documents",
    )
    documents: dict[str, AuthoringDocument] = {}
    for index, raw in enumerate(documents_raw):
        if not isinstance(raw, dict):
            raise MirrorMaterializationError(f"documents[{index}] must be an object")
        authoring_name = _require_string(
            raw.get("authoring"),
            context=f"documents[{index}].authoring",
        )
        source_name = _require_string(
            raw.get("source"),
            context=f"documents[{index}].source",
        )
        if authoring_name in documents:
            raise MirrorMaterializationError(
                f"Duplicate authoring manifest document: {authoring_name}"
            )
        authoring_path = _resolve_inside(
            authoring_root,
            authoring_name,
            context=f"documents[{index}].authoring",
        )
        source_path = _resolve_inside(
            source_root,
            source_name,
            context=f"documents[{index}].source",
        )
        if not authoring_path.is_file() or authoring_path.suffix.lower() != ".svg":
            raise MirrorMaterializationError(
                f"Authoring SVG is missing: {authoring_path}"
            )
        if not source_path.is_file() or source_path.suffix.lower() != ".svg":
            raise MirrorMaterializationError(f"Lossless source SVG is missing: {source_path}")

        expected_source_sha = _require_string(
            raw.get("source_sha256"),
            context=f"documents[{index}].source_sha256",
        )
        actual_source_sha = _sha256_file(source_path)
        if actual_source_sha != expected_source_sha:
            raise MirrorMaterializationError(
                f"Lossless source SVG changed: {source_path.name}; expected "
                f"{expected_source_sha}, found {actual_source_sha}"
            )

        refs_raw = raw.get("source_refs")
        if not isinstance(refs_raw, dict):
            raise MirrorMaterializationError(
                f"documents[{index}].source_refs must be an object"
            )
        refs: dict[str, SourceRefRecord] = {}
        source_root_element = _parse_svg(source_path)
        for source_ref, ref_raw in refs_raw.items():
            if not isinstance(source_ref, str) or not isinstance(ref_raw, dict):
                raise MirrorMaterializationError(
                    f"Invalid source-ref record in {authoring_name}"
                )
            path_raw = ref_raw.get("source_path")
            if not (
                isinstance(path_raw, list)
                and all(isinstance(item, int) and item >= 0 for item in path_raw)
            ):
                raise MirrorMaterializationError(
                    f"{authoring_name} source ref {source_ref!r} has invalid source_path"
                )
            initial_hash = _require_string(
                ref_raw.get("initial_authoring_subtree_sha256"),
                context=f"{authoring_name} source ref {source_ref!r} hash",
            )
            source_path_tuple = tuple(path_raw)
            source_element = _source_element(source_root_element, source_path_tuple)
            if _source_identity(source_element) != source_ref:
                raise MirrorMaterializationError(
                    f"{authoring_name} source ref {source_ref!r} resolves to "
                    f"{_source_identity(source_element)!r}"
                )
            refs[source_ref] = SourceRefRecord(source_path_tuple, initial_hash)

        documents[authoring_name] = AuthoringDocument(
            name=authoring_name,
            authoring_path=authoring_path,
            source_path=source_path,
            source_sha256=expected_source_sha,
            source_refs=refs,
        )

    actual_authoring_files = {
        path.relative_to(authoring_root).as_posix()
        for path in authoring_root.rglob("*.svg")
        if path.is_file()
    }
    if actual_authoring_files != set(documents):
        raise MirrorMaterializationError(
            "Authoring manifest/file roster differs; missing="
            f"{sorted(set(documents) - actual_authoring_files)}, extra="
            f"{sorted(actual_authoring_files - set(documents))}"
        )
    if manifest.get("file_count") != len(documents):
        raise MirrorMaterializationError(
            "authoring_manifest.json file_count does not match documents"
        )
    expected_ref_count = sum(len(document.source_refs) for document in documents.values())
    if manifest.get("source_ref_count") != expected_ref_count:
        raise MirrorMaterializationError(
            "authoring_manifest.json source_ref_count does not match documents"
        )
    return authoring_root, documents


def _load_vector_assets(
    workspace: Path,
    documents: dict[str, AuthoringDocument],
) -> dict[str, VectorAssetRecord]:
    inventory_path = workspace / "authoring-svg_vector_asset_inventory.json"
    if not inventory_path.exists():
        return {}
    inventory = _load_json(inventory_path, context="vector asset inventory")
    if inventory.get("schema") != VECTOR_INVENTORY_SCHEMA:
        raise MirrorMaterializationError(
            f"Unsupported vector inventory schema: {inventory.get('schema')!r}"
        )
    if inventory.get("icon_namespace") != IMPORTED_ICON_NAMESPACE:
        raise MirrorMaterializationError(
            "Mirror vector inventory must use icon_namespace='imported'"
        )
    icons_root = workspace / "icons"
    records: dict[str, VectorAssetRecord] = {}
    for index, raw in enumerate(
        _require_list(inventory.get("assets"), context="vector inventory assets")
    ):
        if not isinstance(raw, dict):
            raise MirrorMaterializationError(f"vector assets[{index}] must be an object")
        icon = _require_string(raw.get("icon"), context=f"assets[{index}].icon")
        asset = _require_string(raw.get("asset"), context=f"assets[{index}].asset")
        origin = _require_string(raw.get("svg"), context=f"assets[{index}].svg")
        if not icon.startswith(f"{IMPORTED_ICON_NAMESPACE}/"):
            raise MirrorMaterializationError(
                f"Vector asset {icon!r} is outside imported/ namespace"
            )
        if icon in records:
            raise MirrorMaterializationError(f"Duplicate vector asset id: {icon}")
        if origin not in documents:
            raise MirrorMaterializationError(
                f"Vector asset {icon!r} names unknown origin document {origin!r}"
            )
        asset_path = _resolve_inside(
            icons_root,
            asset,
            context=f"vector asset {icon!r}",
        )
        if not asset_path.is_file() or asset_path.suffix.lower() != ".svg":
            raise MirrorMaterializationError(f"Vector asset is missing: {asset_path}")
        expected_sha256 = _require_string(
            raw.get("asset_sha256"),
            context=f"vector asset {icon!r} asset_sha256",
        )
        actual_sha256 = _sha256_file(asset_path)
        if actual_sha256 != expected_sha256:
            raise MirrorMaterializationError(
                f"Vector asset {icon!r} changed; expected {expected_sha256}, "
                f"found {actual_sha256}"
            )
        refs_raw = _require_list(
            raw.get("source_refs", []),
            context=f"vector asset {icon!r} source_refs",
        )
        if not all(isinstance(item, str) and item for item in refs_raw):
            raise MirrorMaterializationError(
                f"Vector asset {icon!r} source_refs must be strings"
            )
        records[icon] = VectorAssetRecord(
            icon=icon,
            asset_path=asset_path,
            origin_document=origin,
            expected_sha256=expected_sha256,
            source_refs=tuple(refs_raw),
        )
    if inventory.get("asset_count") != len(records):
        raise MirrorMaterializationError("Vector inventory asset_count is stale")
    return records


def _source_ref_counts(root: ET.Element) -> dict[str, int]:
    counts: dict[str, int] = {}
    for element in root.iter():
        source_ref = element.get(SOURCE_REF_ATTRIBUTE)
        if source_ref:
            counts[source_ref] = counts.get(source_ref, 0) + 1
    return counts


def _imported_icon_refs(root: ET.Element) -> set[str]:
    return {
        value
        for element in root.iter()
        if (value := (element.get("data-icon") or "").strip())
        if value.startswith(f"{IMPORTED_ICON_NAMESPACE}/")
    }


def _validate_source_ref_closure(
    documents: dict[str, AuthoringDocument],
    vector_assets: dict[str, VectorAssetRecord],
) -> set[str]:
    direct_counts: dict[str, dict[str, int]] = {}
    referenced_icons: set[str] = set()
    for name, document in documents.items():
        root = _parse_svg(document.authoring_path)
        direct_counts[name] = _source_ref_counts(root)
        referenced_icons.update(_imported_icon_refs(root))

    unknown_icons = sorted(referenced_icons - set(vector_assets))
    if unknown_icons:
        raise MirrorMaterializationError(
            "Authoring SVG references missing imported vector asset(s): "
            + ", ".join(unknown_icons)
        )

    asset_counts_by_document: dict[str, dict[str, int]] = {
        name: {} for name in documents
    }
    for icon in sorted(referenced_icons):
        record = vector_assets[icon]
        root = _parse_svg(record.asset_path)
        counts = _source_ref_counts(root)
        if set(counts) != set(record.source_refs):
            raise MirrorMaterializationError(
                f"Vector asset {icon!r} source-ref inventory is stale; expected "
                f"{sorted(record.source_refs)}, found {sorted(counts)}"
            )
        duplicate_refs = sorted(ref for ref, count in counts.items() if count != 1)
        if duplicate_refs:
            raise MirrorMaterializationError(
                f"Vector asset {icon!r} contains duplicate source refs: "
                + ", ".join(duplicate_refs)
            )
        origin_counts = asset_counts_by_document[record.origin_document]
        for source_ref, count in counts.items():
            origin_counts[source_ref] = origin_counts.get(source_ref, 0) + count

    for name, document in documents.items():
        combined = dict(direct_counts[name])
        for source_ref, count in asset_counts_by_document[name].items():
            combined[source_ref] = combined.get(source_ref, 0) + count
        expected = set(document.source_refs)
        actual = set(combined)
        duplicates = sorted(ref for ref, count in combined.items() if count != 1)
        if actual != expected or duplicates:
            raise MirrorMaterializationError(
                f"{name} source-ref closure differs from its manifest; missing="
                f"{sorted(expected - actual)}, extra={sorted(actual - expected)}, "
                f"duplicates={duplicates}"
            )
    return referenced_icons


def _load_native_graph(workspace: Path) -> dict[str, Any]:
    native_path = workspace / "native_structure.json"
    native = _load_json(native_path, context="native structure")
    if native.get("schema") != NATIVE_STRUCTURE_SCHEMA:
        raise MirrorMaterializationError(
            f"Unsupported native structure schema: {native.get('schema')!r}"
        )
    source = native.get("source")
    if not isinstance(source, dict):
        raise MirrorMaterializationError("native_structure.json source must be an object")
    template_name = _require_string(
        source.get("templateFile"),
        context="native_structure.json source.templateFile",
    )
    template_path = _resolve_inside(
        workspace,
        template_name,
        context="native source template",
    )
    if not template_path.is_file():
        raise MirrorMaterializationError(f"Native source template is missing: {template_path}")
    expected_sha = _require_string(
        source.get("sha256"),
        context="native_structure.json source.sha256",
    )
    actual_sha = _sha256_file(template_path)
    if actual_sha != expected_sha:
        raise MirrorMaterializationError(
            f"Native source template changed; expected {expected_sha}, found {actual_sha}"
        )

    slide_size = native.get("slideSize")
    if not isinstance(slide_size, dict):
        raise MirrorMaterializationError("native_structure.json slideSize is missing")
    for field in ("width_px", "height_px"):
        value = slide_size.get(field)
        if not isinstance(value, (int, float)) or not math.isfinite(value) or value <= 0:
            raise MirrorMaterializationError(f"native slideSize.{field} must be positive")
    return native


def _load_inheritance(workspace: Path) -> dict[str, Any]:
    return _load_json(workspace / "svg" / "inheritance.json", context="inheritance graph")


def _validate_graph_roster(
    native: dict[str, Any],
    inheritance: dict[str, Any],
    documents: dict[str, AuthoringDocument],
) -> None:
    masters = _require_list(native.get("masters"), context="native masters")
    layouts = _require_list(native.get("layouts"), context="native layouts")
    slides = _require_list(native.get("slides"), context="native slides")
    inheritance_masters_raw = _require_list(
        inheritance.get("masters"),
        context="inheritance masters",
    )
    master_file_by_part = {
        _require_string(item.get("partPath"), context="inheritance Master partPath"):
        _require_string(item.get("file"), context="inheritance Master file")
        for item in inheritance_masters_raw
        if isinstance(item, dict)
    }
    for index, master in enumerate(masters):
        if not isinstance(master, dict):
            raise MirrorMaterializationError(f"native masters[{index}] must be an object")
        package_part = _require_string(
            master.get("packagePart"),
            context=f"native masters[{index}].packagePart",
        )
        svg_file = master_file_by_part.get(package_part)
        if svg_file is None:
            raise MirrorMaterializationError(
                f"Native Master part {package_part!r} has no inheritance SVG file"
            )
        master["svgFile"] = svg_file
    expected_files: set[str] = set()
    for collection, field in (
        (masters, "svgFile"),
        (layouts, "svgFile"),
        (slides, "layeredSvgFile"),
    ):
        for index, item in enumerate(collection):
            if not isinstance(item, dict):
                raise MirrorMaterializationError(f"native {field}[{index}] must be an object")
            expected_files.add(
                _require_string(item.get(field), context=f"native {field}[{index}]")
            )
    if expected_files != set(documents):
        raise MirrorMaterializationError(
            "Native graph and authoring document roster differ; missing="
            f"{sorted(expected_files - set(documents))}, extra="
            f"{sorted(set(documents) - expected_files)}"
        )

    inherited_masters = {
        _require_string(item.get("file"), context="inheritance master file")
        for item in inheritance_masters_raw
        if isinstance(item, dict)
    }
    inherited_layouts = {
        _require_string(item.get("file"), context="inheritance layout file"): item
        for item in _require_list(inheritance.get("layouts"), context="inheritance layouts")
        if isinstance(item, dict)
    }
    inherited_slides = {
        int(item.get("index")): item
        for item in _require_list(inheritance.get("slides"), context="inheritance slides")
        if isinstance(item, dict) and isinstance(item.get("index"), int)
    }
    if inherited_masters != {item["svgFile"] for item in masters}:
        raise MirrorMaterializationError("Inheritance Master roster differs from native graph")
    if set(inherited_layouts) != {item["svgFile"] for item in layouts}:
        raise MirrorMaterializationError("Inheritance Layout roster differs from native graph")
    if set(inherited_slides) != {int(item["index"]) for item in slides}:
        raise MirrorMaterializationError("Inheritance Slide roster differs from native graph")

    master_file_by_key = {item["key"]: item["svgFile"] for item in masters}
    layout_file_by_key = {item["key"]: item["svgFile"] for item in layouts}
    for layout in layouts:
        inherited = inherited_layouts[layout["svgFile"]]
        if inherited.get("master") != master_file_by_key.get(layout.get("masterKey")):
            raise MirrorMaterializationError(
                f"Layout {layout.get('key')!r} Master parent differs across native facts"
            )
        inherited_visibility = _require_boolean(
            inherited.get("showMasterShapes"),
            context=f"inheritance Layout {layout.get('key')!r} showMasterShapes",
        )
        native_visibility = _require_boolean(
            layout.get("showMasterShapes"),
            context=f"native Layout {layout.get('key')!r} showMasterShapes",
        )
        if inherited_visibility != native_visibility:
            raise MirrorMaterializationError(
                f"Layout {layout.get('key')!r} showMasterShapes differs across facts"
            )
    for slide in slides:
        inherited = inherited_slides[int(slide["index"])]
        if inherited.get("layout") != layout_file_by_key.get(slide.get("layoutKey")):
            raise MirrorMaterializationError(
                f"Slide {slide.get('index')} Layout parent differs across native facts"
            )
        if inherited.get("master") != master_file_by_key.get(slide.get("masterKey")):
            raise MirrorMaterializationError(
                f"Slide {slide.get('index')} Master parent differs across native facts"
            )
        inherited_visibility = _require_boolean(
            inherited.get("showInheritedShapes"),
            context=f"inheritance Slide {slide.get('index')} showInheritedShapes",
        )
        native_visibility = _require_boolean(
            slide.get("showInheritedShapes"),
            context=f"native Slide {slide.get('index')} showInheritedShapes",
        )
        if inherited_visibility != native_visibility:
            raise MirrorMaterializationError(
                f"Slide {slide.get('index')} showInheritedShapes differs across facts"
            )


def _absolutize_local_hrefs(root: ET.Element, base_dir: Path) -> None:
    for element in root.iter():
        for attribute in ("href", f"{{{XLINK_NS}}}href"):
            value = element.get(attribute)
            if not value or value.startswith("#"):
                continue
            parsed = urlsplit(value)
            if parsed.scheme or parsed.netloc or not parsed.path:
                continue
            absolute = (base_dir / unquote(parsed.path)).resolve().as_posix()
            element.set(
                attribute,
                urlunsplit(("", "", absolute, parsed.query, parsed.fragment)),
            )


def _rehydrate_tree(
    root: ET.Element,
    document: AuthoringDocument,
    *,
    excluded_refs: set[str],
) -> RestorationStats:
    source_root = _parse_svg(document.source_path)
    stats = RestorationStats()

    def restore(element: ET.Element) -> ET.Element:
        source_ref = element.get(SOURCE_REF_ATTRIBUTE)
        record = document.source_refs.get(source_ref or "")
        if source_ref and record is None:
            raise MirrorMaterializationError(
                f"{document.name} contains unknown source ref {source_ref!r}"
            )
        if source_ref and source_ref in excluded_refs:
            stats.structural_refs += 1
        elif source_ref and record is not None:
            actual_hash = semantic_subtree_sha256(
                element,
                ignored_attributes=frozenset({SOURCE_REF_ATTRIBUTE}),
            )
            if actual_hash == record.initial_authoring_subtree_sha256:
                restored = copy.deepcopy(_source_element(source_root, record.source_path))
                _absolutize_local_hrefs(restored, document.source_path.parent)
                stats.rehydrated_refs += 1
                return restored
            stats.fallback_refs += 1

        children = list(element)
        for index, child in enumerate(children):
            replacement = restore(child)
            if replacement is child:
                continue
            replacement.tail = child.tail
            element.remove(child)
            element.insert(index, replacement)
        return element

    restore(root)
    return stats


def _line_groups(text: ET.Element) -> list[list[ET.Element]]:
    """Return direct tspan runs grouped by their imported visual line."""
    groups: list[list[ET.Element]] = []
    for tspan in text:
        if _local_name(tspan.tag) != "tspan":
            return []
        raw_dy = _optional_float(tspan.get("dy"))
        starts_line = (
            not groups
            or tspan.get("x") is not None
            or tspan.get("y") is not None
            or (raw_dy is not None and abs(raw_dy) > 1e-9)
        )
        if starts_line:
            groups.append([tspan])
        else:
            groups[-1].append(tspan)
    return groups


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


def _text_body_segments(metadata: ET.Element) -> list[list[str]] | None:
    if metadata.get("data-pptx-encoding") != "base64":
        return None
    try:
        payload = base64.b64decode((metadata.text or "").strip(), validate=True)
        text_body = ET.fromstring(payload)
    except (ValueError, ET.ParseError):
        return None

    paragraphs: list[list[str]] = []
    for paragraph in text_body:
        if _local_name(paragraph.tag) != "p":
            continue
        segments = [""]
        for child in paragraph:
            tag = _local_name(child.tag)
            if tag == "br":
                segments.append("")
            elif tag in {"r", "fld"}:
                segments[-1] += "".join(
                    node.text or ""
                    for node in child.iter()
                    if _local_name(node.tag) == "t"
                )
        paragraphs.append(segments)
    return paragraphs


def _mark_explicit_breaks(
    authored_element: ET.Element,
    source_element: ET.Element,
) -> None:
    metadata = next(
        (
            child
            for child in source_element
            if _local_name(child.tag) == "metadata"
            and child.get("data-pptx-part") == "txbody"
        ),
        None,
    )
    if metadata is None:
        return
    paragraphs = _text_body_segments(metadata)
    if paragraphs is None:
        return
    authored_texts = [
        child for child in authored_element if _local_name(child.tag) == "text"
    ]
    if len(authored_texts) != len(paragraphs):
        return

    pending: list[ET.Element] = []
    for text, segments in zip(authored_texts, paragraphs):
        groups = _line_groups(text)
        if not groups:
            if len(segments) > 1:
                return
            continue
        line_texts = [
            _normalized_text(
                "".join(text for node in group for text in node.itertext())
            )
            for group in groups
        ]
        cursor = 0
        for segment_index, segment in enumerate(segments):
            target = _normalized_text(segment)
            combined = ""
            start = cursor
            while cursor < len(line_texts):
                combined = _normalized_text(" ".join((combined, line_texts[cursor])))
                cursor += 1
                if combined == target:
                    break
                if target and not target.startswith(combined):
                    return
            if combined != target:
                return
            if segment_index > 0 and start < len(groups):
                pending.append(groups[start][0])
        if cursor != len(groups):
            return
    for tspan in pending:
        tspan.set("data-paragraph-soft-break", "0")


def _annotate_unchanged_explicit_text_breaks(
    root: ET.Element,
    document: AuthoringDocument,
    excluded_refs: set[str],
) -> None:
    """Recover native a:br semantics without restoring a stale txBody."""
    if not excluded_refs:
        return
    source_root = _parse_svg(document.source_path)
    for element in root.iter():
        source_ref = element.get(SOURCE_REF_ATTRIBUTE)
        record = document.source_refs.get(source_ref or "")
        if source_ref not in excluded_refs or record is None:
            continue
        actual_hash = semantic_subtree_sha256(
            element,
            ignored_attributes=frozenset({SOURCE_REF_ATTRIBUTE}),
        )
        if actual_hash != record.initial_authoring_subtree_sha256:
            continue
        _mark_explicit_breaks(
            element,
            _source_element(source_root, record.source_path),
        )


def _prepared_document(
    document: AuthoringDocument,
    *,
    excluded_refs: set[str],
) -> tuple[ET.Element, RestorationStats]:
    root = _parse_svg(document.authoring_path)
    _absolutize_local_hrefs(root, document.authoring_path.parent)
    stats = _rehydrate_tree(root, document, excluded_refs=excluded_refs)
    _annotate_unchanged_explicit_text_breaks(root, document, excluded_refs)
    return root, stats


def _safe_prefix(value: str) -> str:
    normalized = _SAFE_KEY_RE.sub("-", value).strip("-.")
    return normalized or "node"


def _namespace_ids(root: ET.Element, prefix: str) -> None:
    mapping: dict[str, str] = {}
    for element in root.iter():
        element_id = element.get("id")
        if element_id:
            mapping[element_id] = f"{prefix}{_safe_prefix(element_id)}"
    for element in root.iter():
        element_id = element.get("id")
        if element_id:
            element.set("id", mapping[element_id])
        for name, value in list(element.attrib.items()):
            if name == "id":
                continue
            if value.startswith("#") and value[1:] in mapping:
                element.set(name, f"#{mapping[value[1:]]}")
                continue
            rewritten = _URL_REFERENCE_RE.sub(
                lambda match: f"url(#{mapping.get(match.group(2), match.group(2))})",
                value,
            )
            if name.rsplit("}", 1)[-1] in {"aria-describedby", "aria-labelledby"}:
                rewritten = " ".join(mapping.get(token, token) for token in rewritten.split())
            element.set(name, rewritten)
        if _local_name(element.tag) == "style" and element.text:
            element.text = _CSS_ID_RE.sub(
                lambda match: f"#{mapping.get(match.group(1), match.group(1))}",
                element.text,
            )


def _parse_style(value: str | None) -> dict[str, str]:
    declarations: dict[str, str] = {}
    for raw in (value or "").split(";"):
        if ":" not in raw:
            continue
        name, declaration = raw.split(":", 1)
        if name.strip() and declaration.strip():
            declarations[name.strip().lower()] = declaration.strip()
    return declarations


def _style_text(declarations: dict[str, str]) -> str:
    return ";".join(f"{name}:{value}" for name, value in declarations.items())


def _hidden(element: ET.Element) -> bool:
    style = _parse_style(element.get("style"))
    display = (element.get("display") or style.get("display") or "").strip().lower()
    visibility = (
        element.get("visibility") or style.get("visibility") or ""
    ).strip().lower()
    opacity = (element.get("opacity") or style.get("opacity") or "").strip()
    if display == "none" or visibility in {"hidden", "collapse"}:
        return True
    try:
        return bool(opacity) and float(opacity) <= 0
    except ValueError:
        return False


def _paint_value(element: ET.Element, name: str) -> str | None:
    value = element.get(name)
    if value is not None:
        return value.strip().lower()
    return _parse_style(element.get("style")).get(name)


def _visible_leaf(element: ET.Element) -> bool:
    if _hidden(element):
        return False
    tag = _local_name(element.tag)
    if tag in _NON_VISUAL_TAGS:
        return False
    if tag in {"g", "svg", "a"}:
        return any(_visible_leaf(child) for child in element)
    if tag in {"rect", "image", "foreignObject"}:
        for dimension in ("width", "height"):
            raw = element.get(dimension)
            if raw is not None:
                try:
                    if float(raw) <= 0:
                        return False
                except ValueError:
                    pass
        return True
    if tag in {"line", "polyline"}:
        stroke = _paint_value(element, "stroke")
        return stroke not in {None, "none", "transparent"}
    if tag in {"path", "polygon", "circle", "ellipse"}:
        fill = _paint_value(element, "fill")
        stroke = _paint_value(element, "stroke")
        return not (
            fill in {"none", "transparent"}
            and stroke in {None, "none", "transparent"}
        )
    if tag == "text":
        return bool("".join(element.itertext()).strip())
    return True


def _merge_group_inheritance(parent: ET.Element, child: ET.Element) -> None:
    for name in _INHERITED_PRESENTATION_ATTRIBUTES:
        if parent.get(name) is not None and child.get(name) is None:
            child.set(name, parent.get(name, ""))
    parent_style = _parse_style(parent.get("style"))
    child_style = _parse_style(child.get("style"))
    if parent_style:
        merged = dict(parent_style)
        merged.update(child_style)
        child.set("style", _style_text(merged))
    parent_transform = (parent.get("transform") or "").strip()
    child_transform = (child.get("transform") or "").strip()
    if parent_transform:
        child.set(
            "transform",
            " ".join(item for item in (parent_transform, child_transform) if item),
        )


def _flatten_fixed_group(group: ET.Element, *, context: str) -> list[ET.Element]:
    visible_children = [child for child in group if _visible_leaf(child)]
    group_style = _parse_style(group.get("style"))
    aggregate_attrs = {
        name for name in _AGGREGATE_GROUP_ATTRIBUTES if group.get(name) is not None
    }
    aggregate_attrs.update(
        name
        for name in _AGGREGATE_GROUP_ATTRIBUTES
        if name in group_style
    )
    if len(visible_children) > 1 and aggregate_attrs:
        raise MirrorMaterializationError(
            f"{context} cannot expand a multi-child group with aggregate effect(s): "
            + ", ".join(sorted(aggregate_attrs))
        )

    atoms: list[ET.Element] = []
    for child in visible_children:
        child_style = _parse_style(child.get("style"))
        conflicting_aggregate_attrs = {
            name
            for name in aggregate_attrs
            if child.get(name) is not None or name in child_style
        }
        if conflicting_aggregate_attrs:
            raise MirrorMaterializationError(
                f"{context} cannot collapse nested aggregate effect(s): "
                + ", ".join(sorted(conflicting_aggregate_attrs))
            )
        item = copy.deepcopy(child)
        _merge_group_inheritance(group, item)
        for name in aggregate_attrs:
            if group.get(name) is not None:
                item.set(name, group.get(name, ""))
        if _local_name(item.tag) == "g":
            atoms.extend(_flatten_fixed_group(item, context=context))
        else:
            atoms.append(item)
    if len(atoms) == 1:
        atom = atoms[0]
        for name in ("data-pptx-frame", "data-pptx-object", "data-pptx-prst"):
            if group.get(name) is not None and atom.get(name) is None:
                atom.set(name, group.get(name, ""))
    return atoms


def _flatten_fixed_text_atoms(atoms: Iterable[ET.Element]) -> list[ET.Element]:
    flattened: list[ET.Element] = []
    for atom in atoms:
        if _local_name(atom.tag) != "text":
            flattened.append(atom)
            continue
        scratch = ET.Element(f"{{{SVG_NS}}}svg")
        scratch.append(atom)
        tree = ET.ElementTree(scratch)
        flatten_text_with_tspans(tree, merge_paragraphs=False)
        flattened.extend(list(scratch))
    return flattened


def _fixed_atoms(
    root: ET.Element,
    *,
    scope: str,
    key: str,
    placeholder_source_refs: set[str] | None = None,
) -> list[ET.Element]:
    atoms: list[ET.Element] = []
    serial = 0
    placeholder_source_refs = placeholder_source_refs or set()
    for child in root:
        tag = _local_name(child.tag)
        if (
            tag in _NON_VISUAL_TAGS
            or child.get("data-ph-type") is not None
            or child.get(SOURCE_REF_ATTRIBUTE) in placeholder_source_refs
        ):
            continue
        if not _visible_leaf(child):
            continue
        if tag == "g":
            expanded = _flatten_fixed_group(
                child,
                context=f"{scope} {key} element {child.get('id') or '<g>'}",
            )
        else:
            expanded = [copy.deepcopy(child)]
        expanded = _flatten_fixed_text_atoms(expanded)
        source_ref = child.get(SOURCE_REF_ATTRIBUTE)
        source_token = source_ref.split(":", 1)[-1] if source_ref else str(serial + 1)
        for part, atom in enumerate(expanded, start=1):
            serial += 1
            atom.set(
                "id",
                f"{scope}-{_safe_prefix(key)}-{_safe_prefix(source_token)}-{part}",
            )
            atom.set("data-pptx-layer", scope)
            atom.set("data-pptx-editable", "false")
            atom.attrib.pop("data-ph-type", None)
            atoms.append(atom)
    return atoms


def _frame(element: ET.Element | None) -> tuple[float, float, float, float] | None:
    if element is None:
        return None
    raw = (element.get("data-pptx-frame") or "").replace(",", " ").split()
    if len(raw) != 4:
        return None
    try:
        values = tuple(float(item) for item in raw)
    except ValueError:
        return None
    if not all(math.isfinite(item) for item in values) or values[2] <= 0 or values[3] <= 0:
        return None
    return values


def _element_by_ref(root: ET.Element, source_ref: str) -> ET.Element | None:
    matches = [
        element
        for element in root.iter()
        if element.get(SOURCE_REF_ATTRIBUTE) == source_ref
    ]
    if len(matches) > 1:
        raise MirrorMaterializationError(f"Duplicate source ref in document: {source_ref}")
    return matches[0] if matches else None


def _placeholder_guide_by_semantic(
    root: ET.Element,
    semantic_role: str,
) -> ET.Element | None:
    matches = []
    for element in root.iter():
        placeholder_type = element.get("data-ph-type")
        if not placeholder_type:
            continue
        if _semantic_role({"type": placeholder_type}) == semantic_role:
            matches.append(element)
    if len(matches) == 1:
        return matches[0]
    if matches or semantic_role != "object":
        return None

    body_matches = [
        element
        for element in root.iter()
        if element.get("data-ph-type") == "body"
    ]
    return body_matches[0] if len(body_matches) == 1 else None


def _placeholder_idx(raw: object) -> int | None:
    if raw is None:
        return None
    try:
        value = int(str(raw))
    except ValueError as exc:
        raise MirrorMaterializationError(f"Invalid placeholder idx: {raw!r}") from exc
    if value < 0:
        raise MirrorMaterializationError(f"Placeholder idx must be non-negative: {raw!r}")
    return value


def _semantic_role(placeholder: dict[str, Any]) -> str:
    role = str(placeholder.get("semanticRole") or "").strip()
    placeholder_type = str(placeholder.get("type") or "").strip()
    mapping = {
        "ctrTitle": "title",
        "title": "title",
        "subTitle": "subtitle",
        "body": "body",
        "dt": "date",
        "ftr": "footer",
        "sldNum": "slide-number",
        "pic": "picture",
        "chart": "chart",
        "tbl": "table",
        "media": "media",
        "obj": "object",
    }
    normalized = role or mapping.get(placeholder_type, "object")
    if normalized not in {
        "title",
        "subtitle",
        "body",
        "picture",
        "chart",
        "table",
        "object",
        "media",
        "date",
        "footer",
        "slide-number",
    }:
        raise MirrorMaterializationError(
            f"Unsupported placeholder semantic role: {normalized!r}"
        )
    return normalized


def _placeholder_match(
    candidate: dict[str, Any],
    target: dict[str, Any],
) -> bool:
    candidate_idx = _placeholder_idx(candidate.get("idx"))
    target_idx = _placeholder_idx(target.get("idx"))
    if candidate_idx == target_idx and _semantic_role(candidate) == _semantic_role(target):
        return True
    return (
        candidate_idx == target_idx
        and str(candidate.get("type") or "") == str(target.get("type") or "")
    )


def _placeholder_with_semantic_fallback(
    candidates: Iterable[dict[str, Any]],
    target: dict[str, Any],
) -> dict[str, Any] | None:
    candidate_list = list(candidates)
    exact = [item for item in candidate_list if _placeholder_match(item, target)]
    if len(exact) == 1:
        return exact[0]
    if len(exact) > 1:
        raise MirrorMaterializationError(
            f"Ambiguous placeholder identity for semantic role {_semantic_role(target)!r}"
        )
    semantic = [
        item
        for item in candidate_list
        if _semantic_role(item) == _semantic_role(target)
    ]
    return semantic[0] if len(semantic) == 1 else None


def _native_geometry(placeholder: dict[str, Any]) -> tuple[float, float, float, float] | None:
    geometry = placeholder.get("geometry")
    if not isinstance(geometry, dict):
        return None
    try:
        values = tuple(float(geometry[name]) for name in ("x", "y", "width", "height"))
    except (KeyError, TypeError, ValueError):
        return None
    if not all(math.isfinite(item) for item in values) or values[2] <= 0 or values[3] <= 0:
        return None
    return values


def _slot_plans(
    layout: dict[str, Any],
    layout_root: ET.Element,
    source_slides: list[dict[str, Any]],
    slide_roots: dict[int, ET.Element],
    master: dict[str, Any],
    master_root: ET.Element,
) -> list[SlotPlan]:
    plans: list[SlotPlan] = []
    placeholders = _require_list(
        layout.get("placeholders", []),
        context=f"layout {layout.get('key')} placeholders",
    )
    master_placeholders = _require_list(
        master.get("placeholders", []),
        context=f"master {master.get('key')} placeholders",
    )
    for raw in placeholders:
        if not isinstance(raw, dict):
            raise MirrorMaterializationError(
                f"Layout {layout.get('key')} placeholder must be an object"
            )
        shape_id = _require_string(
            raw.get("shapeId"),
            context=f"layout {layout.get('key')} placeholder shapeId",
        )
        bounds = _frame(_element_by_ref(layout_root, f"layout:{shape_id}"))
        if bounds is None:
            for slide in source_slides:
                matching = next(
                    (
                        item
                        for item in slide.get("placeholders", [])
                        if isinstance(item, dict) and _placeholder_match(item, raw)
                    ),
                    None,
                )
                if matching is None:
                    continue
                slide_element = _element_by_ref(
                    slide_roots[int(slide["index"])],
                    f"slide:{matching['shapeId']}",
                )
                bounds = _frame(slide_element) or _native_geometry(matching)
                if bounds is not None:
                    break
        if bounds is None:
            matching_master = _placeholder_with_semantic_fallback(
                (item for item in master_placeholders if isinstance(item, dict)),
                raw,
            )
            if matching_master is not None:
                master_element = _element_by_ref(
                    master_root,
                    f"master:{matching_master['shapeId']}",
                )
                bounds = _frame(master_element) or _native_geometry(matching_master)
            else:
                bounds = _frame(
                    _placeholder_guide_by_semantic(master_root, _semantic_role(raw))
                )
        bounds = bounds or _native_geometry(raw)
        if bounds is None:
            raise MirrorMaterializationError(
                f"Layout {layout.get('key')!r} placeholder {shape_id!r} has no "
                "positive deterministic bounds in Layout, Slide, Master, or native facts"
            )
        plans.append(SlotPlan(
            slot_id=f"slot-{_safe_prefix(str(layout['key']))}-{_safe_prefix(shape_id)}",
            semantic_role=_semantic_role(raw),
            placeholder_type=(str(raw.get("type")) if raw.get("type") is not None else None),
            idx=_placeholder_idx(raw.get("idx")),
            shape_id=shape_id,
            bounds=bounds,
        ))
    effective_indices = [plan.idx if plan.idx is not None else 0 for plan in plans]
    if len(effective_indices) != len(set(effective_indices)):
        raise MirrorMaterializationError(
            f"Layout {layout.get('key')!r} has duplicate effective placeholder idx values"
        )
    return plans


def _copy_text_carrier(source: ET.Element | None) -> ET.Element | None:
    if source is None:
        return None
    texts = [element for element in source.iter() if _local_name(element.tag) == "text"]
    if not texts:
        return None
    if len(texts) == 1:
        carrier = copy.deepcopy(texts[0])
    else:
        carrier = ET.Element(f"{{{SVG_NS}}}text", dict(texts[0].attrib))
        previous_y = _optional_float(texts[0].get("y"))
        first_output = True
        for text_index, text in enumerate(texts):
            text_y = _optional_float(text.get("y"))
            text_children = [
                child for child in text if _local_name(child.tag) == "tspan"
            ]
            if text_children:
                for child_index, child in enumerate(text_children):
                    tspan = copy.deepcopy(child)
                    tspan.attrib.pop("y", None)
                    if first_output:
                        tspan.attrib.pop("x", None)
                    elif child_index == 0 and text_y is not None and previous_y is not None:
                        tspan.set("x", text.get("x", texts[0].get("x", "0")))
                        tspan.set("dy", format_coordinate(text_y - previous_y))
                    if text_index > 0 and child_index == 0:
                        tspan.set("data-paragraph-soft-break", "0")
                    carrier.append(tspan)
                    first_output = False
            elif text.text:
                attrs: dict[str, str] = {}
                if not first_output and text_y is not None and previous_y is not None:
                    attrs["x"] = text.get("x", texts[0].get("x", "0"))
                    attrs["dy"] = format_coordinate(text_y - previous_y)
                if text_index > 0:
                    attrs["data-paragraph-soft-break"] = "0"
                tspan = ET.SubElement(carrier, f"{{{SVG_NS}}}tspan", attrs)
                tspan.text = text.text
                first_output = False
            if text_y is not None:
                previous_y = text_y
    carrier.attrib.pop("id", None)
    _normalize_mergeable_tspans(carrier)
    source_frame = _frame(source)
    if source_frame is not None:
        carrier.set(
            "data-pptx-frame",
            " ".join(format_coordinate(value) for value in source_frame),
        )
    carrier.set("data-pptx-carrier", "true")
    return carrier


def _visible_placeholder_decoration(element: ET.Element) -> bool:
    """Return whether one non-text placeholder subtree paints any pixels."""
    if _hidden(element):
        return False
    tag = _local_name(element.tag)
    if tag in _NON_VISUAL_TAGS or tag == "text":
        return False
    if tag in {"g", "svg", "a"}:
        return any(_visible_placeholder_decoration(child) for child in element)
    if tag in {"image", "foreignObject", "use"}:
        return _visible_leaf(element)
    if tag in {"line", "polyline"}:
        stroke = _paint_value(element, "stroke")
        return stroke not in {None, "none", "transparent"}
    if tag in {"rect", "path", "polygon", "circle", "ellipse"}:
        fill = _paint_value(element, "fill")
        stroke = _paint_value(element, "stroke")
        filter_value = _paint_value(element, "filter")
        return (
            fill not in {"none", "transparent"}
            or stroke not in {None, "none", "transparent"}
            or filter_value not in {None, "none"}
        )
    return False


def _placeholder_decorations(source: ET.Element | None) -> list[ET.Element]:
    """Copy direct visual children that decorate one text placeholder."""
    if source is None:
        return []
    decorations = [
        copy.deepcopy(child)
        for child in source
        if _visible_placeholder_decoration(child)
    ]
    for decoration in decorations:
        _merge_group_inheritance(source, decoration)
    _copy_placeholder_native_attributes(source, decorations)
    return decorations


def _copy_placeholder_native_attributes(
    source: ET.Element,
    geometries: list[ET.Element],
) -> None:
    """Carry one logical placeholder geometry's native identity to its leaf."""
    if len(geometries) == 1:
        decoration = geometries[0]
        native_attributes = {
            "data-pptx-frame",
            "data-pptx-geometry-kind",
            "data-pptx-geometry-reason",
            "data-pptx-geometry-sha256",
            "data-pptx-geometry-status",
            "data-pptx-object",
            "data-pptx-prst",
        }
        for name, value in source.attrib.items():
            if name in native_attributes or name.startswith("data-pptx-av-"):
                if decoration.get(name) is None:
                    decoration.set(name, value)


def _placeholder_geometry(source: ET.Element | None) -> list[ET.Element]:
    """Copy direct source geometry even when it has no visible local paint."""
    if source is None:
        return []
    geometry_tags = {
        "circle",
        "ellipse",
        "line",
        "path",
        "polygon",
        "polyline",
        "rect",
    }
    geometries = [
        copy.deepcopy(child)
        for child in source
        if _local_name(child.tag) in geometry_tags and not _hidden(child)
    ]
    for geometry in geometries:
        _merge_group_inheritance(source, geometry)
    _copy_placeholder_native_attributes(source, geometries)
    return geometries


def _apply_placeholder_paint(source: ET.Element, target: ET.Element) -> None:
    """Apply inherited paint without replacing Slide-owned geometry."""
    paint_attributes = {
        "color",
        "fill",
        "fill-opacity",
        "fill-rule",
        "filter",
        "mix-blend-mode",
        "opacity",
        "paint-order",
        "shape-rendering",
        "stroke",
        "stroke-dasharray",
        "stroke-dashoffset",
        "stroke-linecap",
        "stroke-linejoin",
        "stroke-miterlimit",
        "stroke-opacity",
        "stroke-width",
        "vector-effect",
    }
    source_style = _parse_style(source.get("style"))
    target_style = _parse_style(target.get("style"))
    for name in paint_attributes:
        value = source.get(name)
        if value is None:
            value = source_style.get(name)
        if value is None:
            continue
        target.set(name, value)
        target_style.pop(name, None)
    if target_style:
        target.set("style", _style_text(target_style))
    else:
        target.attrib.pop("style", None)


def _remap_placeholder_decorations(
    decorations: list[ET.Element],
    from_frame: tuple[float, float, float, float] | None,
    to_frame: tuple[float, float, float, float] | None,
) -> None:
    """Map inherited decoration geometry onto the effective Slide frame."""
    if from_frame is None or to_frame is None:
        return
    if all(
        math.isclose(source, target, rel_tol=1e-9, abs_tol=1e-9)
        for source, target in zip(from_frame, to_frame)
    ):
        return
    from_x, from_y, from_width, from_height = from_frame
    to_x, to_y, to_width, to_height = to_frame
    scale_x = to_width / from_width
    scale_y = to_height / from_height
    translate_x = to_x - from_x * scale_x
    translate_y = to_y - from_y * scale_y
    frame_transform = "matrix({})".format(
        " ".join((
            _format_number(scale_x),
            "0",
            "0",
            _format_number(scale_y),
            format_coordinate(translate_x),
            format_coordinate(translate_y),
        ))
    )
    for decoration in decorations:
        existing = (decoration.get("transform") or "").strip()
        decoration.set(
            "transform",
            " ".join(value for value in (frame_transform, existing) if value),
        )


def _resolved_placeholder_decorations(
    source: ET.Element | None,
    layout_guide: ET.Element | None,
    master_guide: ET.Element | None,
) -> list[ET.Element]:
    """Resolve text-placeholder decoration through Slide/Layout/Master."""
    local = _placeholder_decorations(source)
    local_geometry = (
        source is not None
        and source.get("data-pptx-placeholder-local-geometry") == "true"
    )
    if local and local_geometry:
        return local

    inherited: list[ET.Element] = []
    inherited_guide: ET.Element | None = None
    for candidate in (layout_guide, master_guide):
        inherited = _placeholder_decorations(candidate)
        if inherited:
            inherited_guide = candidate
            break
    if not inherited:
        return local

    geometry_tags = {
        "circle",
        "ellipse",
        "line",
        "path",
        "polygon",
        "polyline",
        "rect",
    }
    if local and not local_geometry:
        if (
            len(local) != 1
            or len(inherited) != 1
            or _local_name(local[0].tag) not in geometry_tags
            or _local_name(inherited[0].tag) not in geometry_tags
        ):
            return local
        _apply_placeholder_paint(local[0], inherited[0])

    if (
        source is None
        or not local_geometry
        or len(inherited) != 1
    ):
        _remap_placeholder_decorations(
            inherited,
            _frame(inherited_guide),
            _frame(source),
        )
        return inherited

    source_geometry = _placeholder_geometry(source)
    inherited_tag = _local_name(inherited[0].tag)
    if not source_geometry or inherited_tag not in geometry_tags:
        return inherited
    for geometry in source_geometry:
        _apply_placeholder_paint(inherited[0], geometry)
    return source_geometry


def _normalize_mergeable_tspans(text: ET.Element) -> None:
    """Mark the first line of a positional text block for paragraph merging."""
    tspans = [child for child in text if _local_name(child.tag) == "tspan"]
    if len(tspans) < 2 or not any(
        any(tspan.get(name) is not None for name in ("x", "y", "dy"))
        for tspan in tspans
    ):
        return
    first = tspans[0]
    if first.get("x") is None and text.get("x") is not None:
        first.set("x", text.get("x", ""))
    if first.get("y") is None and first.get("dy") is None:
        first.set("dy", "0")


def _blank_text_carrier(
    plan: SlotPlan,
    layout_guide: ET.Element | None,
    master_guide: ET.Element | None,
) -> ET.Element:
    carrier = _copy_text_carrier(layout_guide)
    if carrier is None:
        carrier = _copy_text_carrier(master_guide)
    if carrier is None:
        x, y, _width, height = plan.bounds
        carrier = ET.Element(
            f"{{{SVG_NS}}}text",
            {
                "x": format_coordinate(x),
                "y": format_coordinate(y + min(height, 24)),
                "font-size": "18",
                "fill": "#000000",
                "data-pptx-carrier": "true",
            },
        )
    carrier.text = None
    for child in list(carrier):
        carrier.remove(child)
    if carrier.get("data-pptx-frame") is None:
        carrier.set(
            "data-pptx-frame",
            " ".join(format_coordinate(value) for value in plan.bounds),
        )
    carrier.set("data-pptx-carrier", "true")
    return carrier


def _blank_image_carrier(plan: SlotPlan) -> ET.Element:
    x, y, width, height = plan.bounds
    return ET.Element(
        f"{{{SVG_NS}}}image",
        {
            "x": format_coordinate(x),
            "y": format_coordinate(y),
            "width": format_coordinate(width),
            "height": format_coordinate(height),
            "href": TRANSPARENT_PIXEL_DATA_URI,
            "preserveAspectRatio": "none",
            "data-pptx-carrier": "true",
        },
    )


def _format_number(value: float) -> str:
    return f"{value:.8f}".rstrip("0").rstrip(".") or "0"


def _optional_float(value: str | None) -> float | None:
    if value is None:
        return None
    try:
        parsed = float(value)
    except ValueError:
        return None
    return parsed if math.isfinite(parsed) else None


def _slot_wrapper(
    plan: SlotPlan,
    source: ET.Element | None,
    *,
    layout_guide: ET.Element | None,
    master_guide: ET.Element | None,
) -> tuple[ET.Element, list[ET.Element]]:
    wrapper = ET.Element(
        f"{{{SVG_NS}}}g",
        {
            "id": plan.slot_id,
            "data-pptx-placeholder": plan.semantic_role,
            "data-pptx-bounds": " ".join(
                format_coordinate(item) for item in plan.bounds
            ),
        },
    )
    if plan.idx is not None:
        wrapper.set("data-pptx-idx", str(plan.idx))

    extras: list[ET.Element] = []
    if plan.semantic_role in {
        "title",
        "subtitle",
        "body",
        "date",
        "footer",
        "slide-number",
    }:
        carrier = _copy_text_carrier(source)
        if carrier is None:
            carrier = _blank_text_carrier(plan, layout_guide, master_guide)
        extras = _resolved_placeholder_decorations(
            source,
            layout_guide,
            master_guide,
        )
        for extra in extras:
            extra.attrib.pop("data-pptx-carrier", None)
        wrapper.append(carrier)
        return wrapper, extras

    if plan.semantic_role == "object":
        proxy_source = source
        if proxy_source is None:
            proxy_source = layout_guide
        if proxy_source is None:
            proxy_source = master_guide
        if proxy_source is None:
            raise MirrorMaterializationError(
                f"Object slot {plan.slot_id!r} has no visible proxy source"
            )
        visible = [copy.deepcopy(child) for child in proxy_source if _visible_leaf(child)]
        if not visible:
            raise MirrorMaterializationError(
                f"Object slot {plan.slot_id!r} has no visible proxy content"
            )
        wrapper.set("data-pptx-binding", "proxy")
        for child in visible:
            wrapper.append(child)
        return wrapper, extras

    expected_tags = {
        "picture": {"image", "svg"},
        "media": {"image", "svg"},
        "chart": {"g"},
        "table": {"g"},
    }[plan.semantic_role]
    carrier_source = source or layout_guide
    candidates = [
        element
        for element in (carrier_source.iter() if carrier_source is not None else [])
        if _local_name(element.tag) in expected_tags and _visible_leaf(element)
    ]
    if (
        not candidates
        and source is None
        and layout_guide is not None
        and plan.semantic_role in {"picture", "media"}
    ):
        wrapper.append(_blank_image_carrier(plan))
        return wrapper, extras
    if len(candidates) != 1:
        raise MirrorMaterializationError(
            f"Slot {plan.slot_id!r} requires exactly one visible "
            f"{plan.semantic_role} carrier, found {len(candidates)}"
        )
    carrier = copy.deepcopy(candidates[0])
    if plan.semantic_role in {"chart", "table"}:
        marker = carrier.get("data-pptx-replace-with")
        if marker != plan.semantic_role:
            raise MirrorMaterializationError(
                f"Slot {plan.slot_id!r} {plan.semantic_role} carrier lacks native marker"
            )
    carrier.set("data-pptx-carrier", "true")
    wrapper.append(carrier)
    return wrapper, extras


def _strip_source_refs(root: ET.Element) -> None:
    for element in root.iter():
        element.attrib.pop(SOURCE_REF_ATTRIBUTE, None)
        element.attrib.pop("data-ph-type", None)


def _is_full_canvas_rect(
    element: ET.Element,
    width: float,
    height: float,
) -> bool:
    if _local_name(element.tag) != "rect":
        return False
    try:
        values = tuple(
            float(element.get(name, default))
            for name, default in (
                ("x", "0"),
                ("y", "0"),
                ("width", "0"),
                ("height", "0"),
            )
        )
    except ValueError:
        return False
    fill = _paint_value(element, "fill")
    return (
        math.isclose(values[0], 0, abs_tol=0.01)
        and math.isclose(values[1], 0, abs_tol=0.01)
        and math.isclose(values[2], width, abs_tol=0.01)
        and math.isclose(values[3], height, abs_tol=0.01)
        and fill not in {None, "none", "transparent"}
    )


def _non_visual_nodes(roots: Iterable[ET.Element]) -> tuple[ET.Element | None, list[ET.Element]]:
    definitions: list[ET.Element] = []
    styles: list[ET.Element] = []
    for root in roots:
        for child in root:
            tag = _local_name(child.tag)
            if tag == "defs":
                definitions.extend(copy.deepcopy(list(child)))
            elif tag == "style":
                styles.append(copy.deepcopy(child))
    defs_node = ET.Element(f"{{{SVG_NS}}}defs") if definitions else None
    if defs_node is not None:
        for definition in definitions:
            defs_node.append(definition)
    return defs_node, styles


def _slide_placeholder_map(slide: dict[str, Any]) -> dict[str, dict[str, Any]]:
    return {
        f"slide:{item['shapeId']}": item
        for item in slide.get("placeholders", [])
        if isinstance(item, dict) and item.get("shapeId") is not None
    }


def _matching_slide_placeholder(
    slide: dict[str, Any],
    layout_placeholder: dict[str, Any],
) -> dict[str, Any] | None:
    matches = [
        item
        for item in slide.get("placeholders", [])
        if isinstance(item, dict) and _placeholder_match(item, layout_placeholder)
    ]
    if len(matches) > 1:
        raise MirrorMaterializationError(
            f"Slide {slide.get('index')} ambiguously matches Layout placeholder "
            f"{layout_placeholder.get('shapeId')}"
        )
    return matches[0] if matches else None


def _compose_template(
    *,
    native: dict[str, Any],
    master: dict[str, Any],
    layout: dict[str, Any],
    master_root: ET.Element,
    layout_root: ET.Element,
    slide: dict[str, Any] | None,
    slide_root: ET.Element | None,
    slot_plans: list[SlotPlan],
) -> ET.Element:
    width = float(native["slideSize"]["width_px"])
    height = float(native["slideSize"]["height_px"])
    root = ET.Element(
        f"{{{SVG_NS}}}svg",
        {
            "version": "1.1",
            "width": format_coordinate(width),
            "height": format_coordinate(height),
            "viewBox": (
                f"0 0 {format_coordinate(width)} {format_coordinate(height)}"
            ),
            "data-pptx-master": str(master["key"]),
            "data-pptx-master-name": str(master["name"]),
            "data-pptx-layout": str(layout["key"]),
            "data-pptx-layout-name": str(layout["name"]),
            "data-pptx-show-master-shapes": str(
                _require_boolean(
                    layout.get("showMasterShapes"),
                    context=f"Layout {layout.get('key')!r} showMasterShapes",
                )
            ).lower(),
            "data-pptx-show-inherited-shapes": str(
                _require_boolean(
                    slide.get("showInheritedShapes"),
                    context=f"Slide {slide.get('index')} showInheritedShapes",
                )
                if slide
                else True
            ).lower(),
        },
    )

    roots = [master_root, layout_root]
    if slide_root is not None:
        roots.append(slide_root)
    defs, styles = _non_visual_nodes(roots)
    if defs is not None:
        root.append(defs)
    for style in styles:
        root.append(style)

    master_atoms = _fixed_atoms(master_root, scope="master", key=str(master["key"]))
    layout_placeholder_refs = {
        f"layout:{item['shapeId']}"
        for item in layout.get("placeholders", [])
        if isinstance(item, dict) and item.get("shapeId") is not None
    }
    layout_atoms = _fixed_atoms(
        layout_root,
        scope="layout",
        key=str(layout["key"]),
        placeholder_source_refs=layout_placeholder_refs,
    )
    master_backgrounds = [
        atom for atom in master_atoms if _is_full_canvas_rect(atom, width, height)
    ]
    layout_backgrounds = [
        atom for atom in layout_atoms if _is_full_canvas_rect(atom, width, height)
    ]
    master_shapes = [atom for atom in master_atoms if atom not in master_backgrounds]
    layout_shapes = [atom for atom in layout_atoms if atom not in layout_backgrounds]

    slide_backgrounds: list[ET.Element] = []
    slide_content: list[ET.Element] = []
    source_placeholder_elements: dict[str, ET.Element] = {}
    if slide_root is not None and slide is not None:
        placeholder_refs = _slide_placeholder_map(slide)
        for index, child in enumerate(slide_root):
            if _local_name(child.tag) in _NON_VISUAL_TAGS:
                continue
            source_ref = child.get(SOURCE_REF_ATTRIBUTE)
            if source_ref in placeholder_refs:
                source_placeholder_elements[source_ref] = child
                continue
            item = copy.deepcopy(child)
            if _is_full_canvas_rect(item, width, height):
                item.set("id", item.get("id") or f"slide-{slide['index']}-background")
                item.set("data-pptx-layer", "slide")
                item.set("data-pptx-editable", "false")
                slide_backgrounds.append(item)
            else:
                item.set("id", item.get("id") or f"slide-{slide['index']}-node-{index + 1}")
                slide_content.append(item)
        if len(slide_backgrounds) > 1:
            raise MirrorMaterializationError(
                f"Slide {slide['index']} has more than one full-canvas solid background"
            )

    layout_placeholders = {
        str(item["shapeId"]): item
        for item in layout.get("placeholders", [])
        if isinstance(item, dict) and item.get("shapeId") is not None
    }
    master_placeholders = [
        item for item in master.get("placeholders", []) if isinstance(item, dict)
    ]
    slots: list[ET.Element] = []
    slot_extras: list[ET.Element] = []
    for plan in slot_plans:
        layout_placeholder = layout_placeholders[plan.shape_id]
        layout_guide = _element_by_ref(layout_root, f"layout:{plan.shape_id}")
        master_placeholder = _placeholder_with_semantic_fallback(
            master_placeholders,
            layout_placeholder,
        )
        master_guide = (
            _element_by_ref(master_root, f"master:{master_placeholder['shapeId']}")
            if master_placeholder is not None
            else _placeholder_guide_by_semantic(
                master_root,
                plan.semantic_role,
            )
        )
        source_element = None
        if slide is not None:
            slide_placeholder = _matching_slide_placeholder(slide, layout_placeholder)
            if slide_placeholder is not None:
                source_element = source_placeholder_elements.get(
                    f"slide:{slide_placeholder['shapeId']}"
                )
                if source_element is None:
                    raise MirrorMaterializationError(
                        f"Slide {slide['index']} placeholder {slide_placeholder['shapeId']} "
                        "is missing from its authoring SVG"
                    )
        wrapper, extras = _slot_wrapper(
            plan,
            source_element,
            layout_guide=layout_guide,
            master_guide=master_guide,
        )
        slots.append(wrapper)
        slot_extras.extend(extras)

    for element in (
        *master_backgrounds,
        *layout_backgrounds,
        *slide_backgrounds,
        *master_shapes,
        *layout_shapes,
        *slide_content,
        *slot_extras,
        *slots,
    ):
        _strip_source_refs(element)
        root.append(element)
    _strip_source_refs(root)
    return root


def _page_type(slide: dict[str, Any]) -> str:
    raw = str(slide.get("pageType") or "").strip().lower()
    mapping = {
        "cover_candidate": "cover",
        "toc_candidate": "toc",
        "chapter_candidate": "chapter",
        "content_candidate": "content",
        "ending_candidate": "ending",
    }
    return mapping.get(raw, "content")


def _serialize_svg(root: ET.Element) -> bytes:
    payload = ET.tostring(root, encoding="utf-8", xml_declaration=False)
    return payload if payload.endswith(b"\n") else payload + b"\n"


def _refresh_preset_preview_hashes(root: ET.Element) -> None:
    """Rebind imported preset guards after deterministic SVG ID namespacing."""
    for element in root.iter():
        if (
            _local_name(element.tag) == "g"
            and element.get("data-pptx-object") in {"shape", "connector"}
            and element.get("data-pptx-prst") is not None
            and element.get("data-pptx-preview-sha256") is not None
        ):
            fingerprint = svg_preset_preview_fingerprint(element)
            element.set("data-pptx-preview-sha256", fingerprint)
            for descendant in element.iter():
                if descendant.get("data-pptx-preview-sha256") is not None:
                    descendant.set("data-pptx-preview-sha256", fingerprint)


def _sanitize_connector_references(root: ET.Element) -> int:
    """Drop endpoint bindings whose native target is absent from this SVG tree."""
    identities = {
        (
            element.get("data-pptx-shape-scope"),
            element.get("data-pptx-shape-id"),
        )
        for element in root.iter()
        if element.get("data-pptx-shape-scope")
        and element.get("data-pptx-shape-id")
    }
    detached: set[tuple[str, str, str]] = set()
    for element in root.iter():
        connector_scope = element.get("data-pptx-shape-scope") or "unknown"
        connector_id = element.get("data-pptx-shape-id") or element.get("id") or "unknown"
        for endpoint in ("start", "end"):
            target_id_attr = f"data-pptx-{endpoint}-shape-id"
            target_id = element.get(target_id_attr)
            if target_id is None:
                continue
            target_scope = element.get(
                f"data-pptx-{endpoint}-shape-scope",
                connector_scope,
            )
            if (target_scope, target_id) in identities:
                continue
            detached.add((connector_scope, connector_id, endpoint))
            for suffix in ("shape-id", "shape-scope", "site"):
                element.attrib.pop(f"data-pptx-{endpoint}-{suffix}", None)
    return len(detached)


def _local_asset_path(value: str, base_dir: Path) -> Path | None:
    if not value or value.startswith("#"):
        return None
    parsed = urlsplit(value)
    if parsed.scheme == "file":
        return Path(unquote(parsed.path)).resolve()
    if parsed.scheme or parsed.netloc or not parsed.path:
        return None
    path = Path(unquote(parsed.path))
    return path.resolve() if path.is_absolute() else (base_dir / path).resolve()


def _detected_bitmap_extension(path: Path) -> str | None:
    header = path.read_bytes()[:32]
    if header.startswith(b"\x89PNG\r\n\x1a\n"):
        return ".png"
    if header.startswith(b"\xff\xd8\xff"):
        return ".jpg"
    if header.startswith((b"GIF87a", b"GIF89a")):
        return ".gif"
    if header.startswith(b"BM"):
        return ".bmp"
    if header.startswith((b"II*\x00", b"MM\x00*")):
        return ".tiff"
    if len(header) >= 12 and header[:4] == b"RIFF" and header[8:12] == b"WEBP":
        return ".webp"
    return None


def _rewrite_packaged_assets(
    root: ET.Element,
    *,
    source_base: Path,
    final_svg_path: Path,
    template_workspace: Path,
    asset_sources: dict[Path, Path],
) -> None:
    for element in root.iter():
        for attribute in ("href", f"{{{XLINK_NS}}}href"):
            value = element.get(attribute)
            if value is None:
                continue
            source = _local_asset_path(value, source_base)
            if source is None:
                continue
            if not source.is_file():
                raise MirrorMaterializationError(
                    f"Referenced local asset is missing: {source}"
                )
            detected_extension = _detected_bitmap_extension(source)
            if detected_extension is not None:
                source_suffix = source.suffix.lower()
                equivalent_suffixes = (
                    {".jpg", ".jpeg"}
                    if detected_extension == ".jpg"
                    else {detected_extension}
                )
                packaged_name = (
                    source.name
                    if source_suffix in equivalent_suffixes
                    else f"{source.stem}{detected_extension}"
                )
                relative_target = Path("images") / packaged_name
            elif source.suffix.lower() in _BITMAP_EXTENSIONS:
                relative_target = Path("images") / source.name
            else:
                relative_target = Path("templates") / "assets" / source.name
            previous = asset_sources.get(relative_target)
            if previous is not None and _sha256_file(previous) != _sha256_file(source):
                raise MirrorMaterializationError(
                    f"Asset basename collision with different content: {source.name}"
                )
            asset_sources[relative_target] = source
            target = template_workspace / relative_target
            relative_href = os.path.relpath(target, final_svg_path.parent).replace(os.sep, "/")
            parsed = urlsplit(value)
            element.set(
                attribute,
                urlunsplit(("", "", relative_href, parsed.query, parsed.fragment)),
            )


def _materialize_icon(
    record: VectorAssetRecord,
    document: AuthoringDocument,
) -> tuple[ET.Element, RestorationStats]:
    actual_sha256 = _sha256_file(record.asset_path)
    if actual_sha256 != record.expected_sha256:
        raise MirrorMaterializationError(
            f"Vector asset {record.icon!r} changed during materialization; "
            f"expected {record.expected_sha256}, found {actual_sha256}"
        )
    root = _parse_svg(record.asset_path)
    _absolutize_local_hrefs(root, record.asset_path.parent)
    stats = _rehydrate_tree(root, document, excluded_refs=set())
    _strip_source_refs(root)
    return root, stats


def _ensure_no_source_refs(path: Path) -> None:
    root = _parse_svg(path)
    refs = sorted(_source_ref_counts(root))
    if refs:
        raise MirrorMaterializationError(
            f"Materialized file still contains source refs: {path}: {refs[:5]}"
        )


def _preflight_output(
    import_workspace: Path,
    template_workspace: Path,
    relative_files: Iterable[Path],
) -> None:
    import_resolved = import_workspace.resolve()
    output_resolved = template_workspace.resolve()
    try:
        output_resolved.relative_to(import_resolved)
    except ValueError:
        pass
    else:
        raise MirrorMaterializationError(
            "Template workspace must not be inside the import workspace"
        )
    try:
        import_resolved.relative_to(output_resolved)
    except ValueError:
        pass
    else:
        raise MirrorMaterializationError(
            "Import workspace must not be inside the template workspace"
        )

    templates_root = template_workspace / "templates"
    if templates_root.exists():
        if not templates_root.is_dir():
            raise MirrorMaterializationError(
                f"Template output is not a directory: {templates_root}"
            )
        existing = sorted(path for path in templates_root.iterdir())
        if existing:
            raise MirrorMaterializationError(
                f"Template output must be empty before mirror materialization: "
                f"{templates_root}; first entry: {existing[0].name}"
            )
    collisions = [
        template_workspace / relative
        for relative in relative_files
        if os.path.lexists(template_workspace / relative)
    ]
    if collisions:
        raise MirrorMaterializationError(
            f"Output file already exists: {collisions[0]}"
        )


def _nearest_existing_directory(path: Path) -> Path:
    candidate = path
    while not candidate.exists():
        if candidate.parent == candidate:
            break
        candidate = candidate.parent
    if not candidate.is_dir():
        raise MirrorMaterializationError(f"Output parent is not a directory: {candidate}")
    return candidate


def _ensure_directory(path: Path, created: list[Path]) -> None:
    missing: list[Path] = []
    candidate = path
    while not candidate.exists():
        if os.path.lexists(candidate):
            raise MirrorMaterializationError(
                f"Output parent is not a directory: {candidate}"
            )
        missing.append(candidate)
        candidate = candidate.parent
    if not candidate.is_dir():
        raise MirrorMaterializationError(f"Output parent is not a directory: {candidate}")
    for directory in reversed(missing):
        directory.mkdir()
        created.append(directory)


def _publish_files(
    template_workspace: Path,
    staged_root: Path,
    relative_files: list[Path],
) -> None:
    created_dirs: list[Path] = []
    published: list[Path] = []
    try:
        for relative in relative_files:
            target = template_workspace / relative
            _ensure_directory(target.parent, created_dirs)
        staging_device = staged_root.stat().st_dev
        for relative in relative_files:
            target = template_workspace / relative
            if target.parent.stat().st_dev != staging_device:
                raise MirrorMaterializationError(
                    f"Cannot atomically publish across filesystems: {target}"
                )
            if os.path.lexists(target):
                raise MirrorMaterializationError(
                    f"Output appeared while mirror files were staged: {target}"
                )
        for relative in relative_files:
            target = template_workspace / relative
            (staged_root / relative).replace(target)
            published.append(target)
    except (OSError, MirrorMaterializationError) as exc:
        rollback_errors: list[str] = []
        for path in reversed(published):
            try:
                path.unlink(missing_ok=True)
            except OSError as rollback_exc:
                rollback_errors.append(f"could not remove {path}: {rollback_exc}")
        for directory in reversed(created_dirs):
            try:
                directory.rmdir()
            except OSError:
                pass
        if rollback_errors:
            raise MirrorMaterializationError(
                f"Mirror publish failed ({exc}); rollback was incomplete: "
                + "; ".join(rollback_errors)
            ) from exc
        raise MirrorMaterializationError(f"Mirror publish failed: {exc}") from exc


def materialize_mirror_template(
    import_workspace: Path,
    template_workspace: Path,
) -> dict[str, Any]:
    """Validate one Type A import graph and publish its mirror SVG contract."""
    authoring_root, documents = _load_authoring_documents(import_workspace)
    vector_assets = _load_vector_assets(import_workspace, documents)
    referenced_icons = _validate_source_ref_closure(documents, vector_assets)
    native = _load_native_graph(import_workspace)
    inheritance = _load_inheritance(import_workspace)
    _validate_graph_roster(native, inheritance, documents)

    masters = {item["key"]: item for item in native["masters"]}
    layouts = {item["key"]: item for item in native["layouts"]}
    slides = sorted(native["slides"], key=lambda item: int(item["index"]))
    if [int(item["index"]) for item in slides] != list(range(1, len(slides) + 1)):
        raise MirrorMaterializationError(
            "Mirror source slide indexes must be contiguous and start at 1"
        )

    prepared_masters: dict[str, ET.Element] = {}
    prepared_layouts: dict[str, ET.Element] = {}
    prepared_slides: dict[int, ET.Element] = {}
    total_stats = RestorationStats()
    for key, master in masters.items():
        document = documents[master["svgFile"]]
        root, stats = _prepared_document(
            document,
            excluded_refs=set(document.source_refs),
        )
        _namespace_ids(root, f"m-{_safe_prefix(str(key))}-")
        prepared_masters[key] = root
        total_stats.merge(stats)
    for key, layout in layouts.items():
        document = documents[layout["svgFile"]]
        root, stats = _prepared_document(
            document,
            excluded_refs=set(document.source_refs),
        )
        _namespace_ids(root, f"l-{_safe_prefix(str(key))}-")
        prepared_layouts[key] = root
        total_stats.merge(stats)
    for slide in slides:
        index = int(slide["index"])
        document = documents[slide["layeredSvgFile"]]
        placeholder_refs = {
            f"slide:{item['shapeId']}"
            for item in slide.get("placeholders", [])
            if isinstance(item, dict) and item.get("shapeId") is not None
        }
        root, stats = _prepared_document(document, excluded_refs=placeholder_refs)
        _namespace_ids(root, f"s-{index:03d}-")
        prepared_slides[index] = root
        total_stats.merge(stats)

    slides_by_layout: dict[str, list[dict[str, Any]]] = {key: [] for key in layouts}
    for slide in slides:
        slides_by_layout[str(slide["layoutKey"])].append(slide)

    plans_by_layout: dict[str, list[SlotPlan]] = {}
    for key, layout in layouts.items():
        master_key = str(layout["masterKey"])
        plans_by_layout[key] = _slot_plans(
            layout,
            prepared_layouts[key],
            slides_by_layout[key],
            prepared_slides,
            masters[master_key],
            prepared_masters[master_key],
        )

    materialized_roots: list[tuple[Path, ET.Element]] = []
    for slide in slides:
        index = int(slide["index"])
        layout = layouts[str(slide["layoutKey"])]
        master = masters[str(slide["masterKey"])]
        filename = Path("templates") / f"{index:03d}_{_page_type(slide)}.svg"
        root = _compose_template(
            native=native,
            master=master,
            layout=layout,
            master_root=prepared_masters[str(master["key"])],
            layout_root=prepared_layouts[str(layout["key"])],
            slide=slide,
            slide_root=prepared_slides[index],
            slot_plans=plans_by_layout[str(layout["key"])],
        )
        materialized_roots.append((filename, root))

    unused_layouts = [
        layout for key, layout in layouts.items() if not slides_by_layout[key]
    ]
    for layout in sorted(unused_layouts, key=lambda item: str(item["key"])):
        master = masters[str(layout["masterKey"])]
        filename = Path("templates") / f"layout_{layout['key']}.svg"
        root = _compose_template(
            native=native,
            master=master,
            layout=layout,
            master_root=prepared_masters[str(master["key"])],
            layout_root=prepared_layouts[str(layout["key"])],
            slide=None,
            slide_root=None,
            slot_plans=plans_by_layout[str(layout["key"])],
        )
        materialized_roots.append((filename, root))

    asset_sources: dict[Path, Path] = {}
    files: list[MaterializedFile] = []
    output_roots: list[tuple[Path, ET.Element]] = []
    native_payloads: dict[str, bytes] = {}
    native_payload_stats = NativePayloadStats()
    for relative_path, root in materialized_roots:
        final_path = template_workspace / relative_path
        _rewrite_packaged_assets(
            root,
            source_base=authoring_root,
            final_svg_path=final_path,
            template_workspace=template_workspace,
            asset_sources=asset_sources,
        )
        total_stats.detached_connector_endpoints += _sanitize_connector_references(root)
        total_stats.upright_text_compensations += (
            _compensate_reflected_group_text(root)
        )
        compact_svg_tree(root, compact_native_frames=False)
        _refresh_preset_preview_hashes(root)
        try:
            native_payload_stats.merge(
                externalize_native_payloads(root, native_payloads)
            )
        except NativePayloadError as exc:
            raise MirrorMaterializationError(
                f"Cannot externalize native payloads in {relative_path}: {exc}"
            ) from exc
        output_roots.append((relative_path, root))

    for icon in sorted(referenced_icons):
        record = vector_assets[icon]
        icon_root, stats = _materialize_icon(
            record,
            documents[record.origin_document],
        )
        total_stats.merge(stats)
        relative_path = Path("icons") / f"{icon}.svg"
        final_path = template_workspace / relative_path
        _rewrite_packaged_assets(
            icon_root,
            source_base=record.asset_path.parent,
            final_svg_path=final_path,
            template_workspace=template_workspace,
            asset_sources=asset_sources,
        )
        total_stats.detached_connector_endpoints += _sanitize_connector_references(
            icon_root
        )
        compact_svg_tree(icon_root, compact_native_frames=False)
        _refresh_preset_preview_hashes(icon_root)
        try:
            native_payload_stats.merge(
                externalize_native_payloads(icon_root, native_payloads)
            )
        except NativePayloadError as exc:
            raise MirrorMaterializationError(
                f"Cannot externalize native payloads in {relative_path}: {exc}"
            ) from exc
        output_roots.append((relative_path, icon_root))

    native_record_keys: set[str] = set()
    try:
        for _relative_path, root in output_roots:
            native_record_keys.update(
                collect_native_attribute_record_keys(root)
            )
        native_record_ids, native_records = build_native_attribute_records(
            native_record_keys
        )
        for _relative_path, root in output_roots:
            native_payload_stats.merge(
                externalize_native_attribute_records(root, native_record_ids)
            )
    except NativePayloadError as exc:
        raise MirrorMaterializationError(
            f"Cannot externalize native attribute records: {exc}"
        ) from exc

    for relative_path, root in output_roots:
        files.append(MaterializedFile(relative_path, _serialize_svg(root)))
    execution_manifest_path = Path("templates") / TEMPLATE_EXECUTION_MANIFEST_NAME
    files.extend(
        _template_execution_manifest_files(
            materialized_roots,
            _source_import_summary(import_workspace),
        )
    )

    for relative_target, source in sorted(asset_sources.items()):
        files.append(MaterializedFile(relative_target, source.read_bytes()))
    payload_store: bytes | None = None
    if native_payloads or native_records:
        try:
            payload_store = serialize_native_payload_store(
                native_payloads,
                native_records,
            )
        except NativePayloadError as exc:
            raise MirrorMaterializationError(
                f"Cannot serialize native payload store: {exc}"
            ) from exc
        files.append(
            MaterializedFile(PAYLOAD_STORE_RELATIVE_PATH, payload_store)
        )
    relative_files = [item.relative_path for item in files]
    if len(relative_files) != len(set(relative_files)):
        raise MirrorMaterializationError("Materializer produced duplicate output paths")
    _preflight_output(import_workspace, template_workspace, relative_files)

    staging_parent = _nearest_existing_directory(template_workspace.parent)
    with tempfile.TemporaryDirectory(
        prefix=".mirror-template-materialize-",
        dir=staging_parent,
    ) as temporary:
        staged_root = Path(temporary) / "staged"
        for item in files:
            target = staged_root / item.relative_path
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes(item.payload)

        template_paths = sorted(
            staged_root / relative
            for relative, _root in materialized_roots
        )
        try:
            parse_template_slides(template_paths)
        except TemplateStructureError as exc:
            raise MirrorMaterializationError(
                f"Materialized structured SVG contract is invalid: {exc}"
            ) from exc
        for relative in relative_files:
            if relative.suffix.lower() == ".svg":
                staged_svg = staged_root / relative
                try:
                    hydrate_native_payload_refs(_parse_svg(staged_svg), staged_svg)
                except NativePayloadError as exc:
                    raise MirrorMaterializationError(
                        f"Materialized native payload reference is invalid: "
                        f"{relative}: {exc}"
                    ) from exc
                _ensure_no_source_refs(staged_svg)
        _publish_files(
            template_workspace,
            staged_root,
            sorted(relative_files, key=lambda item: item.as_posix()),
        )

    return {
        "schema": "ppt-master.mirror-materialization-report.v1",
        "import_workspace": str(import_workspace),
        "template_workspace": str(template_workspace),
        "source_slide_indexes": [int(item["index"]) for item in slides],
        "source_slide_count": len(slides),
        "master_count": len(masters),
        "layout_count": len(layouts),
        "unused_layout_count": len(unused_layouts),
        "template_svg_count": len(materialized_roots),
        "template_execution_manifest": execution_manifest_path.as_posix(),
        "template_text_slot_manifest_count": len(materialized_roots),
        "imported_vector_count": len(referenced_icons),
        "packaged_asset_count": len(asset_sources),
        "restoration": total_stats.as_dict(),
        "native_payloads": {
            **native_payload_stats.as_dict(),
            "unique_count": len(native_payloads),
            "unique_record_count": len(native_records),
            "unique_raw_bytes": sum(len(payload) for payload in native_payloads.values()),
            "store_bytes": len(payload_store or b""),
            "store_path": (
                PAYLOAD_STORE_RELATIVE_PATH.as_posix()
                if payload_store is not None
                else None
            ),
        },
        "files": [
            item.relative_path.as_posix()
            for item in sorted(files, key=lambda item: item.relative_path.as_posix())
        ],
    }


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description=(
            "Materialize a deterministic mirror template from one PPTX import "
            "workspace's layered authoring IR."
        )
    )
    parser.add_argument(
        "import_workspace",
        type=Path,
        help="Type A PPTX import workspace containing authoring-svg/ and native facts",
    )
    parser.add_argument(
        "template_workspace",
        type=Path,
        help="Empty template workspace destination (templates/ must be absent or empty)",
    )
    return parser


def main(argv: list[str] | None = None) -> int:
    args = build_parser().parse_args(argv)
    import_workspace = args.import_workspace.resolve()
    template_workspace = args.template_workspace.resolve()
    if not import_workspace.is_dir():
        print(f"Error: import workspace does not exist: {import_workspace}", file=sys.stderr)
        return 1
    try:
        report = materialize_mirror_template(import_workspace, template_workspace)
    except (MirrorMaterializationError, OSError) as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1
    print(json.dumps(report, ensure_ascii=False, indent=2))
    return 0


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