#!/usr/bin/env python3
"""Engine E1 PPTX in-place remediation (file-remediation Stage 2, Office route).

Same shape as remediate_docx.py: copy input -> edit only the copy -> always
appearance-check via LibreOffice (skipped, not failed, if absent) -> optional
--export-pdf (lo_export.py, Impress path, artifact-postfix applied inside).

judgment.json schema:
{
  "doc_title": "string, optional",
  "language": "en-US (default)",
  "alt_text": {"<shape_index>": "alt text"},
      # shape_index = 0-based, across ALL picture shapes in slide order
      # (document-wide index, not per-slide). python-pptx sets descr to the
      # source filename by default on add_picture — treat that as missing.
  "slide_titles": {"<slide_index>": "title text"},
      # slide_index = 0-based. Only applied where a title/ctrTitle
      # placeholder already exists on that slide (slide.shapes.title is not
      # None). # ponytail: injecting placeholders into blank layouts is
      # deferred — a titleless slide is reported in result.edits.flags, not
      # guessed at.
  "header_tables": [<table_index>, ...]
      # table_index = 0-based, across ALL table shapes in slide order
      # (document-wide index). Sets table.first_row = True.
}

Auto-fixes needing no judgment: NONE — same v1 stance as remediate_docx.py.

Usage:
    python remediate_pptx.py --input FILE [--output FILE] [--judgment judgment.json]
                              [--export-pdf OUT.pdf] [--json]
Exit: 0 ok, 1 appearance check exceeded threshold (output kept), 2 error.
"""

from __future__ import annotations

import argparse
import json
import shutil
import sys
import tempfile
from pathlib import Path

SCRIPTS_DIR = Path(__file__).resolve().parent
if str(SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPTS_DIR))

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

import fidelity_gate
import lo_export
import verapdf_gate

APPEARANCE_THRESHOLD_PCT = 0.5


def _validate_judgment(judgment):
    if not isinstance(judgment, dict):
        raise ValueError("judgment must be a JSON object")
    if "alt_text" in judgment and not isinstance(judgment["alt_text"], dict):
        raise ValueError("judgment.alt_text must be an object {index: alt text}")
    if "slide_titles" in judgment and not isinstance(judgment["slide_titles"], dict):
        raise ValueError("judgment.slide_titles must be an object {index: title text}")
    if "header_tables" in judgment and not isinstance(judgment["header_tables"], list):
        raise ValueError("judgment.header_tables must be a list")


def _walk_pictures(prs):
    return [shape for slide in prs.slides for shape in slide.shapes
            if shape.shape_type == MSO_SHAPE_TYPE.PICTURE]


def _walk_tables(prs):
    return [shape.table for slide in prs.slides for shape in slide.shapes if shape.has_table]


def _appearance_check(input_path, output_path, threshold_pct=APPEARANCE_THRESHOLD_PCT):
    if not lo_export.get_soffice():
        return {"checked": False, "reason": "LibreOffice not installed — appearance check skipped"}
    with tempfile.TemporaryDirectory(prefix="asucb-appearance-") as tmp:
        tmp = Path(tmp)
        pdf_a = lo_export.render_pdf_plain(input_path, tmp / "a.pdf")
        pdf_b = lo_export.render_pdf_plain(output_path, tmp / "b.pdf")
        diff = fidelity_gate.pixel_diff(str(pdf_a), str(pdf_b), threshold_pct=threshold_pct)
    return {
        "checked": True,
        "identical_within_threshold": diff["identical"],
        "worst_pct": diff["worst_pct"],
        "threshold_pct": threshold_pct,
    }


def _export_and_gate(output_path, export_pdf_path):
    pdf_out = lo_export.export_pdf(output_path, export_pdf_path)
    return {"output": str(pdf_out), "verapdf": verapdf_gate.run_verapdf(str(pdf_out))}


def remediate_pptx(input_path, output_path=None, judgment=None, export_pdf=None):
    input_path = Path(input_path)
    if not input_path.exists():
        raise RuntimeError(f"input not found: {input_path}")
    output_path = Path(output_path) if output_path else \
        input_path.with_name(f"{input_path.stem}.remediated.pptx")
    judgment = judgment or {}
    _validate_judgment(judgment)

    shutil.copy2(input_path, output_path)
    prs = Presentation(str(output_path))
    edits = {"alt_set": {}, "titles_set": {}, "header_rows_set": [], "flags": []}

    alt_map = judgment.get("alt_text") or {}
    pics = _walk_pictures(prs)
    for k, v in alt_map.items():
        i = int(k)
        if 0 <= i < len(pics):
            pics[i]._element.nvPicPr.cNvPr.set("descr", v)
            edits["alt_set"][i] = v
        else:
            edits["flags"].append(f"alt_text index out of range: {i}")

    slides = list(prs.slides)
    for k, v in (judgment.get("slide_titles") or {}).items():
        si = int(k)
        if not (0 <= si < len(slides)):
            edits["flags"].append(f"slide_titles index out of range: {si}")
            continue
        title_shape = slides[si].shapes.title
        if title_shape is None:
            # ponytail: injecting placeholders into blank layouts deferred
            edits["flags"].append(f"slide {si}: no title/ctrTitle placeholder — not injected")
            continue
        title_shape.text = v
        edits["titles_set"][si] = v

    tables = _walk_tables(prs)
    for ti in judgment.get("header_tables") or []:
        ti = int(ti)
        if not (0 <= ti < len(tables)):
            edits["flags"].append(f"header_tables index out of range: {ti}")
            continue
        tables[ti].first_row = True
        edits["header_rows_set"].append(ti)

    title = judgment.get("doc_title")
    if title:
        prs.core_properties.title = title
    lang = judgment.get("language")
    if lang:
        prs.core_properties.language = lang

    prs.save(str(output_path))

    result = {"ok": True, "output": str(output_path), "edits": edits}
    result["appearance"] = _appearance_check(input_path, output_path)
    if result["appearance"].get("checked") and not result["appearance"]["identical_within_threshold"]:
        result["ok"] = False
        result["reason"] = "appearance check exceeded fidelity threshold — output kept for review"

    if export_pdf:
        result["export"] = _export_and_gate(output_path, export_pdf)

    return result


def main():
    parser = argparse.ArgumentParser(description="Engine E1 PPTX in-place remediation")
    parser.add_argument("--input", required=True)
    parser.add_argument("--output")
    parser.add_argument("--judgment", help="Path to judgment.json")
    parser.add_argument("--export-pdf", help="Also export a tagged PDF/UA + run the veraPDF gate")
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args()

    judgment = {}
    if args.judgment:
        try:
            judgment = json.loads(Path(args.judgment).read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError) as exc:
            print(f"ERROR: could not read --judgment {args.judgment}: {exc}", file=sys.stderr)
            sys.exit(2)

    try:
        result = remediate_pptx(args.input, args.output, judgment, export_pdf=args.export_pdf)
    except (RuntimeError, ValueError) as exc:
        if args.json:
            print(json.dumps({"ok": False, "error": str(exc)}), file=sys.stderr)
        else:
            print(f"ERROR: {exc}", file=sys.stderr)
        sys.exit(2)

    if args.json:
        print(json.dumps(result, indent=2))
    else:
        status = "OK" if result["ok"] else "APPEARANCE CHECK FAILED"
        print(f"{status}: output={result['output']}")
    sys.exit(0 if result["ok"] else 1)


if __name__ == "__main__":
    main()
