"""experiment_background_prompt_region_ref_assembly_slice — W18F.

Dry-run consumer of the W18E background prompt region/ref appendix.
For each W18E bg, fetch the existing source preview (W15e
per_bg_render_reference_instructions[bg_id].final_prompt_assembly_preview)
and concatenate it with the W18E `background_prompt_region_appendix`
behind a fixed separator. This is a consumer-side preview only — it
does not write back to W15e, does not modify any production
background_prompt file, and does not call any LLM/VLM/image API.

Codex W18F contract:
- Input: a W18E success run dir.
- Walk chain to W15e via W18E → W18D → W18C → W18B → W18A → W15e
  run_meta `args` fields. Each resolution may fail independently; the
  per-bg payload records a BLOCKED_MISSING_SOURCE decision and
  diagnostic when the W15e source preview is unavailable.
- For each W18E bg:
  - carry `bg_id`, `fp_id`, `base_fp_ref_path`,
    `transient_overlay_marker_numbers`,
    `clean_background_expected`.
  - `region_ref_appendix`: verbatim W18E
    `background_prompt_region_appendix`.
  - `base_prompt_source_preview`: W15e per_bg
    `final_prompt_assembly_preview` (string) or empty.
  - `assembled_background_prompt_preview`: deterministic concatenation
    `source_preview + SEPARATOR + region_ref_appendix`. No new prose,
    no rewriting.
  - `consumer_decision_preview`:
    `READY_FOR_BACKGROUND_PROMPT_DRY_RUN` (source present),
    `BLOCKED_MISSING_SOURCE` (source missing).
- Invariant 4 surfaces IMPORTANT diagnostics when a clean BG's source
  preview already references a known transient marker number — old
  residue in W15e that the next migration must clean up.

CLI:
  --derive-region-ref-assembly-from <W18E_run_dir>   (required)
  --target-fp-ids fp_l05_01                          (default, wave-1 lock)
  --output-root <path>
  --diag-print-imports
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple

_REPO_ROOT = Path(__file__).resolve().parents[2]
_SCRIPTS_DIR = _REPO_ROOT / "backend" / "scripts"
if str(_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(_SCRIPTS_DIR))

from experiment_background_pipeline_slice import (  # type: ignore
    KST,
    PLAN_VERSION,
    _check_image_imports_present,
    _check_production_diff_empty,
    _maybe_print_imports,
)
from experiment_floor_plan_grid_layout_slice import (  # type: ignore
    W16_ALLOWED_TARGET_FP_IDS,
    _resolve_targets,
)
from experiment_floor_plan_base_layout_vlm_readback_slice import (  # type: ignore
    _resolve_w18a_run_dir_from_w18b,
)
from experiment_floor_plan_bg_to_fp_region_ref_slice import (  # type: ignore
    _resolve_w18b_run_dir_from_w18c,
)

W18F_STAGE = "w18f_background_prompt_region_ref_assembly_slice"

_DEFAULT_OUTPUT_ROOT = (
    _REPO_ROOT
    / "scripts_output"
    / "background_prompt_region_ref_assembly_slice_experiment"
)

_ASSEMBLY_SEPARATOR = (
    "\n\n----- background_prompt_region_appendix (W18E preview) -----\n\n"
)

_NO_TRANSIENT_SENTINEL = (
    "No transient overlay markers for this background."
)

_DECISION_READY = "READY_FOR_BACKGROUND_PROMPT_DRY_RUN"
_DECISION_BLOCKED = "BLOCKED_MISSING_SOURCE"
_ALLOWED_DECISIONS = {_DECISION_READY, _DECISION_BLOCKED}

_MARKER_NUMBER_PAT = re.compile(r"#(\d+)")


def _run_id() -> str:
    import secrets

    return datetime.now(KST).strftime("%Y%m%d_%H%M") + "_" + secrets.token_hex(3)


def _parse_args(argv):
    p = argparse.ArgumentParser(
        description=(
            "W18F background prompt region/ref assembly dry-run — "
            "deterministic only. No LLM/VLM/image API call. No new prose."
        )
    )
    p.add_argument(
        "--derive-region-ref-assembly-from", required=True,
        help="Path to a prior W18E success run dir.",
    )
    p.add_argument(
        "--target-fp-ids", default="fp_l05_01",
        help="Comma-separated fp_id subset. Wave 1 only allows fp_l05_01.",
    )
    p.add_argument("--output-root", default=str(_DEFAULT_OUTPUT_ROOT))
    p.add_argument("--diag-print-imports", action="store_true")
    return p.parse_args(argv)


def _resolve_path_from_meta(
    meta: Dict[str, Any], key: str, default_parent_subdir: str
) -> Optional[Path]:
    """Resolve a run_dir from a meta args field, falling back to a parent
    sibling if the relative path is missing."""
    args_dict = (meta or {}).get("args") or {}
    if isinstance(args_dict, dict):
        raw = args_dict.get(key)
        if isinstance(raw, str) and raw:
            p = Path(raw)
            if not p.is_absolute():
                p = _REPO_ROOT / raw
            if (p / "run_meta.json").exists():
                return p
    derived_from = (meta or {}).get("derived_from") or ""
    if isinstance(derived_from, str) and derived_from:
        parent = _REPO_ROOT / "scripts_output" / default_parent_subdir
        if (parent / derived_from / "run_meta.json").exists():
            return parent / derived_from
    return None


def _resolve_w18d_run_dir_from_w18e(
    w18e_meta: Dict[str, Any],
) -> Optional[Path]:
    return _resolve_path_from_meta(
        w18e_meta, "derive_region_appendix_from",
        "floor_plan_bg_to_fp_region_ref_slice_experiment",
    )


def _resolve_w18c_run_dir_from_w18d(
    w18d_meta: Dict[str, Any],
) -> Optional[Path]:
    return _resolve_path_from_meta(
        w18d_meta, "derive_region_ref_from",
        "floor_plan_base_layout_vlm_readback_slice_experiment",
    )


def _resolve_w15e_run_dir_from_w18a(
    w18a_meta: Dict[str, Any],
) -> Optional[Path]:
    return _resolve_path_from_meta(
        w18a_meta, "derive_base_layout_from",
        "floor_plan_topology_slice_experiment",
    )


def _build_assembled_prompt_for_bg(
    *, region_ref_appendix: str, source_preview: str,
) -> str:
    """Deterministic concatenation. If source preview is empty, the
    assembled preview is just the appendix (consumer is responsible for
    BLOCKED gating)."""
    if not source_preview:
        return region_ref_appendix
    return source_preview + _ASSEMBLY_SEPARATOR + region_ref_appendix


def _extract_marker_numbers(text: str) -> Set[int]:
    return {int(m.group(1)) for m in _MARKER_NUMBER_PAT.finditer(text or "")}


def _build_assembly_for_fp(
    *, fp_id: str,
    w18e_preview_by_bg: Dict[str, Dict[str, Any]],
    w15e_per_bg: Dict[str, Dict[str, Any]],
) -> Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]]:
    """Return ({bg_id: assembly_payload}, diagnostics)."""
    out: Dict[str, Dict[str, Any]] = {}
    diagnostics: List[Dict[str, Any]] = []

    for bg_id in sorted(w18e_preview_by_bg.keys()):
        w18e_payload = w18e_preview_by_bg.get(bg_id) or {}
        if (w18e_payload.get("fp_id") or "") != fp_id:
            diagnostics.append({
                "bg_id": bg_id, "kind": "fp_id_mismatch",
                "expected": fp_id, "actual": w18e_payload.get("fp_id"),
            })
            continue

        w15e_entry = w15e_per_bg.get(bg_id) or {}
        source_preview = (
            w15e_entry.get("final_prompt_assembly_preview") or ""
        )
        region_ref_appendix = (
            w18e_payload.get("background_prompt_region_appendix") or ""
        )

        if not source_preview:
            diagnostics.append({
                "bg_id": bg_id, "kind": "w15e_source_preview_missing",
                "w15e_entry_present": bool(w15e_entry),
            })

        assembled = _build_assembled_prompt_for_bg(
            region_ref_appendix=region_ref_appendix,
            source_preview=source_preview,
        )

        transient_overlay = (
            w18e_payload.get("transient_overlay_to_describe") or []
        )
        transient_marker_numbers: List[int] = [
            int(t.get("marker_number"))
            for t in transient_overlay
            if isinstance(t, dict)
            and isinstance(t.get("marker_number"), int)
        ]

        clean_background_expected = not transient_marker_numbers
        decision = (
            _DECISION_READY if source_preview else _DECISION_BLOCKED
        )

        out[bg_id] = {
            "bg_id": bg_id,
            "fp_id": fp_id,
            "base_fp_ref_path": w18e_payload.get("base_fp_ref_path") or "",
            "transient_overlay_marker_numbers": transient_marker_numbers,
            "clean_background_expected": clean_background_expected,
            "base_prompt_source_preview": source_preview,
            "region_ref_appendix": region_ref_appendix,
            "assembled_background_prompt_preview": assembled,
            "consumer_decision_preview": decision,
        }

    return out, diagnostics


def _build_w18f_compatibility_report(
    *, assembly_by_bg: Dict[str, Dict[str, Any]],
    w18e_preview_by_bg: Dict[str, Dict[str, Any]],
    transient_marker_set: Set[int],
    target_fp_ids: Set[str],
    production_diff_empty: bool,
    db_write_count: int,
    image_import_seen: bool,
    image_api_call_count: int,
    vlm_api_call_count: int,
    llm_api_call_count: int,
    missing_inputs: List[str],
    prev_run_id: str,
) -> Tuple[dict, List[dict]]:
    inv: Dict[str, Dict[str, Any]] = {}
    important_diagnostics: List[dict] = []

    # 1. inputs_present
    inv["inputs_present"] = {
        "pass": (
            not missing_inputs
            and bool(assembly_by_bg)
            and bool(w18e_preview_by_bg)
        ),
        "detail": {
            "missing_inputs": list(missing_inputs),
            "assembly_bg_count": len(assembly_by_bg),
            "prev_run_id": prev_run_id,
        },
    }

    # 2. target_fp_only_fp_l05_01
    inv["target_fp_only_fp_l05_01"] = {
        "pass": bool(target_fp_ids) and target_fp_ids.issubset(
            W16_ALLOWED_TARGET_FP_IDS
        ),
        "detail": {
            "received_target_fp_ids": sorted(target_fp_ids or set()),
            "allowed": sorted(W16_ALLOWED_TARGET_FP_IDS),
        },
    }

    # 3. assembly_covers_all_w18e_bgs
    expected_bgs = set(w18e_preview_by_bg.keys())
    actual_bgs = set(assembly_by_bg.keys())
    inv["assembly_covers_all_w18e_bgs"] = {
        "pass": expected_bgs == actual_bgs and bool(expected_bgs),
        "detail": {
            "expected_bgs": sorted(expected_bgs),
            "actual_bgs": sorted(actual_bgs),
            "missing_in_w18f": sorted(expected_bgs - actual_bgs),
            "extra_in_w18f": sorted(actual_bgs - expected_bgs),
        },
    }

    # 4. clean_bg_has_no_transient_overlay_in_assembled_prompt
    clean_failures: List[dict] = []
    for bg_id, payload in assembly_by_bg.items():
        if not payload.get("clean_background_expected"):
            continue
        appendix = payload.get("region_ref_appendix") or ""
        source_preview = payload.get("base_prompt_source_preview") or ""

        sentinel_present = _NO_TRANSIENT_SENTINEL in appendix
        appendix_marker_nums = _extract_marker_numbers(appendix)
        transient_in_appendix = sorted(
            appendix_marker_nums & transient_marker_set
        )
        if not sentinel_present:
            clean_failures.append({
                "bg_id": bg_id,
                "reason": "sentinel_missing_in_region_ref_appendix",
            })
        if transient_in_appendix:
            clean_failures.append({
                "bg_id": bg_id,
                "reason": "transient_marker_in_region_ref_appendix",
                "transient_marker_numbers_seen": transient_in_appendix,
            })

        source_marker_nums = _extract_marker_numbers(source_preview)
        transient_in_source = sorted(
            source_marker_nums & transient_marker_set
        )
        if transient_in_source:
            important_diagnostics.append({
                "level": "IMPORTANT",
                "bg_id": bg_id,
                "kind": "clean_bg_source_preview_contains_transient_marker",
                "transient_marker_numbers_in_source_preview": (
                    transient_in_source
                ),
                "policy": (
                    "Clean background's W15e final_prompt_assembly_preview "
                    "still references transient marker numbers. The next "
                    "production background_prompt migration must strip "
                    "these from the source side; W18F does not auto-rewrite."
                ),
            })

        decision = payload.get("consumer_decision_preview")
        if decision not in _ALLOWED_DECISIONS:
            clean_failures.append({
                "bg_id": bg_id,
                "reason": "consumer_decision_preview_invalid_enum",
                "value": decision,
                "allowed": sorted(_ALLOWED_DECISIONS),
            })

    inv["clean_bg_has_no_transient_overlay_in_assembled_prompt"] = {
        "pass": not clean_failures,
        "detail": {
            "failures": clean_failures[:30],
            "failure_count": len(clean_failures),
            "important_source_residue_count": len(important_diagnostics),
            "transient_marker_set": sorted(transient_marker_set),
        },
    }

    # 5. production / api zero guard
    inv["production_diff_zero_db_write_zero_llm_vlm_image_zero"] = {
        "pass": (
            production_diff_empty
            and db_write_count == 0
            and not image_import_seen
            and image_api_call_count == 0
            and vlm_api_call_count == 0
            and llm_api_call_count == 0
        ),
        "detail": {
            "production_diff_empty": production_diff_empty,
            "db_write_count": db_write_count,
            "image_import_seen": image_import_seen,
            "image_api_call_count": image_api_call_count,
            "vlm_api_call_count": vlm_api_call_count,
            "llm_api_call_count": llm_api_call_count,
        },
    }

    all_pass = all(v["pass"] for v in inv.values())
    return {
        "invariants": inv,
        "all_pass": all_pass,
        "important_diagnostics": important_diagnostics,
    }, important_diagnostics


def main(argv=None) -> int:
    args = _parse_args(argv)
    run_id = _run_id()
    out_root = Path(args.output_root)
    run_dir = out_root / run_id
    run_dir.mkdir(parents=True, exist_ok=True)

    prev_run_dir = Path(args.derive_region_ref_assembly_from)
    if not prev_run_dir.is_absolute():
        prev_run_dir = Path.cwd() / prev_run_dir

    target_fp_ids, invalid_targets = _resolve_targets(args.target_fp_ids)

    failed_invariants: List[str] = []
    run_status = "succeeded"
    exit_code = 0

    run_meta: Dict[str, Any] = {
        "run_id": run_id,
        "stage": W18F_STAGE,
        "plan_version": PLAN_VERSION,
        "generated_at": datetime.now(KST).isoformat(),
        "args": vars(args),
        "derived_from": prev_run_dir.name,
        "target_fp_ids": sorted(target_fp_ids),
        "invalid_targets": invalid_targets,
        "llm_api_call_count": 0,
        "vlm_api_call_count": 0,
        "image_api_call_count": 0,
        "outputs": [],
        "run_status": run_status,
        "exit_code": exit_code,
        "failed_invariants": failed_invariants,
        "chain_resolution": {},
    }

    def _persist_and_exit(code: int) -> int:
        run_meta["exit_code"] = code
        if code != 0:
            run_meta["run_status"] = "validation_failed"
        run_meta["failed_invariants"] = failed_invariants
        (run_dir / "run_meta.json").write_text(
            json.dumps(run_meta, ensure_ascii=False, indent=2)
        )
        return code

    missing: List[str] = []
    w18e_preview_path = (
        prev_run_dir / "background_region_ref_prompt_preview.json"
    )
    w18e_meta_path = prev_run_dir / "run_meta.json"
    if not w18e_preview_path.exists():
        missing.append("background_region_ref_prompt_preview.json")
    if not w18e_meta_path.exists():
        missing.append("run_meta.json")
    if missing:
        failed_invariants.append("w18e_inputs_missing")
        run_meta["missing_inputs"] = missing
        return _persist_and_exit(1)
    if invalid_targets:
        failed_invariants.append("invalid_target_fp_ids")
        return _persist_and_exit(1)
    if not target_fp_ids.issubset(W16_ALLOWED_TARGET_FP_IDS):
        failed_invariants.append("target_fp_outside_allowed")
        return _persist_and_exit(1)

    w18e_preview_doc = json.loads(w18e_preview_path.read_text())
    w18e_meta = json.loads(w18e_meta_path.read_text())
    w18e_preview_by_bg: Dict[str, Dict[str, Any]] = (
        w18e_preview_doc.get("preview_by_bg") or {}
    )
    fp_id_doc = w18e_preview_doc.get("fp_id") or ""

    fp_id_first = sorted(target_fp_ids)[0]
    if fp_id_doc and fp_id_doc != fp_id_first:
        failed_invariants.append("w18e_fp_id_mismatch")
        run_meta["w18e_fp_id"] = fp_id_doc
        return _persist_and_exit(1)

    # Chain walk: W18E → W18D → W18C → W18B → W18A → W15e.
    w15e_per_bg: Dict[str, Dict[str, Any]] = {}
    w18a_fp_entry: Dict[str, Any] = {}
    chain: Dict[str, Optional[str]] = {
        "w18d_run_dir": None, "w18c_run_dir": None,
        "w18b_run_dir": None, "w18a_run_dir": None,
        "w15e_run_dir": None,
    }
    chain_diagnostics: List[Dict[str, Any]] = []

    w18d_dir = _resolve_w18d_run_dir_from_w18e(w18e_meta)
    if w18d_dir is None:
        chain_diagnostics.append({"step": "w18d", "reason": "unresolved"})
    else:
        chain["w18d_run_dir"] = w18d_dir.name
        try:
            w18d_meta = json.loads(
                (w18d_dir / "run_meta.json").read_text()
            )
        except Exception:  # noqa: BLE001
            w18d_meta = {}
        w18c_dir = _resolve_w18c_run_dir_from_w18d(w18d_meta)
        if w18c_dir is None:
            chain_diagnostics.append({
                "step": "w18c", "reason": "unresolved",
            })
        else:
            chain["w18c_run_dir"] = w18c_dir.name
            try:
                w18c_meta = json.loads(
                    (w18c_dir / "run_meta.json").read_text()
                )
            except Exception:  # noqa: BLE001
                w18c_meta = {}
            w18b_dir = _resolve_w18b_run_dir_from_w18c(w18c_meta)
            if w18b_dir is None:
                chain_diagnostics.append({
                    "step": "w18b", "reason": "unresolved",
                })
            else:
                chain["w18b_run_dir"] = w18b_dir.name
                try:
                    w18b_meta = json.loads(
                        (w18b_dir / "run_meta.json").read_text()
                    )
                except Exception:  # noqa: BLE001
                    w18b_meta = {}
                w18a_dir = _resolve_w18a_run_dir_from_w18b(w18b_meta)
                if w18a_dir is None:
                    chain_diagnostics.append({
                        "step": "w18a", "reason": "unresolved",
                    })
                else:
                    chain["w18a_run_dir"] = w18a_dir.name
                    try:
                        w18a_meta = json.loads(
                            (w18a_dir / "run_meta.json").read_text()
                        )
                        base_artifact = json.loads(
                            (w18a_dir / "base_layout_prompt.json").read_text()
                        )
                        by_fp = (base_artifact or {}).get(
                            "base_layout_prompt_by_fp"
                        ) or {}
                        w18a_fp_entry = by_fp.get(fp_id_first) or {}
                    except Exception:  # noqa: BLE001
                        w18a_meta = {}
                    w15e_dir = _resolve_w15e_run_dir_from_w18a(w18a_meta)
                    if w15e_dir is None:
                        chain_diagnostics.append({
                            "step": "w15e", "reason": "unresolved",
                        })
                    else:
                        chain["w15e_run_dir"] = w15e_dir.name
                        per_bg_path = (
                            w15e_dir
                            / "per_bg_render_reference_instruction.json"
                        )
                        if per_bg_path.exists():
                            try:
                                per_bg_doc = json.loads(per_bg_path.read_text())
                                w15e_per_bg = (
                                    per_bg_doc.get(
                                        "per_bg_render_reference_instructions"
                                    ) or {}
                                )
                            except Exception:  # noqa: BLE001
                                w15e_per_bg = {}
                        else:
                            chain_diagnostics.append({
                                "step": "w15e",
                                "reason": "per_bg_render_reference_instruction.json_missing",
                            })

    run_meta["chain_resolution"] = chain
    run_meta["chain_diagnostics"] = chain_diagnostics

    transient_marker_set: Set[int] = set()
    for it in w18a_fp_entry.get("excluded_transient_elements") or []:
        if isinstance(it, dict):
            n = it.get("marker_number")
            if isinstance(n, int):
                transient_marker_set.add(n)

    assembly_by_bg, build_diagnostics = _build_assembly_for_fp(
        fp_id=fp_id_first,
        w18e_preview_by_bg=w18e_preview_by_bg,
        w15e_per_bg=w15e_per_bg,
    )

    report, important_diagnostics = _build_w18f_compatibility_report(
        assembly_by_bg=assembly_by_bg,
        w18e_preview_by_bg=w18e_preview_by_bg,
        transient_marker_set=transient_marker_set,
        target_fp_ids=target_fp_ids,
        production_diff_empty=_check_production_diff_empty(),
        db_write_count=0,
        image_import_seen=_check_image_imports_present(),
        image_api_call_count=0,
        vlm_api_call_count=0,
        llm_api_call_count=0,
        missing_inputs=missing,
        prev_run_id=prev_run_dir.name,
    )

    assembly_payload = {
        "assembly_preview_by_bg": assembly_by_bg,
        "fp_id": fp_id_first,
        "derived_from_w18e_run_id": prev_run_dir.name,
        "chain_resolution": chain,
        "transient_marker_set_from_w18a": sorted(transient_marker_set),
        "policy_note": (
            "Dry-run consumer preview only. The W15e per_bg "
            "final_prompt_assembly_preview is treated as the existing "
            "source surface; the W18E background_prompt_region_appendix "
            "is appended behind a fixed separator. Production "
            "background_prompt files, backend/app pipeline, and alembic "
            "are untouched. No prose is generated here."
        ),
        "assembly_build_diagnostics": build_diagnostics,
        "important_diagnostics": important_diagnostics,
    }

    (run_dir / "background_prompt_region_ref_assembly_preview.json").write_text(
        json.dumps(assembly_payload, ensure_ascii=False, indent=2)
    )
    run_meta["outputs"].append(
        "background_prompt_region_ref_assembly_preview.json"
    )

    (run_dir / "w18f_compatibility_report.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2)
    )
    run_meta["outputs"].append("w18f_compatibility_report.json")

    for name, v in report["invariants"].items():
        if not v["pass"] and name not in failed_invariants:
            failed_invariants.append(name)
    if failed_invariants and run_status == "succeeded":
        run_status = "validation_failed"
        exit_code = 1

    run_meta["run_status"] = run_status
    run_meta["exit_code"] = exit_code
    run_meta["failed_invariants"] = failed_invariants
    run_meta["important_diagnostic_count"] = len(important_diagnostics)

    def esc(x):
        return (
            str(x).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
        )

    inv_rows = "".join(
        f"<tr><td>{esc(k)}</td>"
        f"<td class=\"{'pass' if v['pass'] else 'fail'}\">"
        f"{'PASS' if v['pass'] else 'FAIL'}</td>"
        f"<td><pre>{esc(json.dumps(v.get('detail'), ensure_ascii=False))[:900]}</pre></td>"
        f"</tr>"
        for k, v in (report.get("invariants") or {}).items()
    )
    diag_rows = "".join(
        f"<tr><td>{esc(d.get('level'))}</td>"
        f"<td>{esc(d.get('bg_id'))}</td>"
        f"<td>{esc(d.get('kind'))}</td>"
        f"<td><pre>{esc(json.dumps(d, ensure_ascii=False))[:600]}</pre></td></tr>"
        for d in important_diagnostics
    ) or "<tr><td colspan=4>(none)</td></tr>"

    bg_sections: List[str] = []
    for bg_id, payload in assembly_by_bg.items():
        bg_sections.append(
            f"<section><h2>bg: {esc(bg_id)}</h2>"
            f"<p>consumer_decision_preview: <b>{esc(payload.get('consumer_decision_preview'))}</b></p>"
            f"<p>clean_background_expected: <b>{esc(payload.get('clean_background_expected'))}</b></p>"
            f"<p>transient_overlay_marker_numbers: <code>{esc(payload.get('transient_overlay_marker_numbers'))}</code></p>"
            f"<p>base_fp_ref_path: <code>{esc(payload.get('base_fp_ref_path'))}</code></p>"
            f"<h3>base_prompt_source_preview (W15e)</h3>"
            f"<pre>{esc(payload.get('base_prompt_source_preview'))}</pre>"
            f"<h3>region_ref_appendix (W18E)</h3>"
            f"<pre>{esc(payload.get('region_ref_appendix'))}</pre>"
            f"<h3>assembled_background_prompt_preview</h3>"
            f"<pre>{esc(payload.get('assembled_background_prompt_preview'))}</pre>"
            f"</section>"
        )

    (run_dir / "index.html").write_text(
        f"""<!doctype html><html><head><meta charset="utf-8">
<title>W18F background prompt region/ref assembly preview {esc(run_id)}</title>
<style>body{{font-family:sans-serif;margin:1.5em}}
table{{border-collapse:collapse;margin:0.5em 0}}
td,th{{border:1px solid #ccc;padding:4px 8px;vertical-align:top}}
.pass{{color:#080}} .fail{{color:#b00}}
pre{{white-space:pre-wrap;font-size:0.85em;max-width:104ch}}
section{{margin:1.5em 0}}</style></head><body>
<h1>W18F — background_prompt region/ref assembly preview {esc(run_id)}</h1>
<p>derived_from(W18E): {esc(prev_run_dir.name)}
| run_status: <b>{esc(run_status)}</b>
| exit_code: {esc(exit_code)}
| llm/vlm/image api calls: <b>0/0/0</b>
| important_diagnostics: <b>{esc(len(important_diagnostics))}</b></p>

<section><h2>chain resolution</h2>
<pre>{esc(json.dumps(chain, ensure_ascii=False, indent=2))}</pre></section>

<section><h2>important diagnostics (source-side residue, not auto-fixed)</h2>
<table><tr><th>level</th><th>bg_id</th><th>kind</th><th>raw</th></tr>
{diag_rows}</table></section>

{''.join(bg_sections)}

<section><h2>invariants</h2>
<table><tr><th>invariant</th><th>status</th><th>detail</th></tr>
{inv_rows}</table></section>

<details><summary>raw run_meta.json</summary>
<pre>{esc(json.dumps(run_meta, ensure_ascii=False, indent=2))}</pre></details>
</body></html>"""
    )
    run_meta["outputs"].append("index.html")

    (run_dir / "run_meta.json").write_text(
        json.dumps(run_meta, ensure_ascii=False, indent=2)
    )
    _maybe_print_imports(args)
    return exit_code


if __name__ == "__main__":
    sys.exit(main())
