"""One-off: soften content-policy triggers in EP001 SH17 + SH18 plan entries.

Run once. Backs up plan to .bak.<timestamp>. Idempotent — re-running on an
already-softened plan is a no-op.

Triggered by: Test 5D content-policy block (R5 MORNING_REVIEW §"Bugs not
addressed" item 4). Substrate freeze allows plan-data edits.
"""
from __future__ import annotations
import json
import shutil
import sys
import time
from pathlib import Path

PLAN = Path("/Users/joeturnerlin/Dropbox/CLAUDE_DATA/recoil/projects/tartarus/state/visual/plans/ep_001_plan.json")

# (old_substring, new_substring) — applied to every string field of SH17/SH18
# in source_text, prompt_skeleton.*, and compiled_prompts.*
SWAPS = [
    ("Combat chassis", "Industrial chassis"),
    ("combat chassis", "industrial chassis"),
    ("Military architecture built for damage", "Heavy industrial architecture"),
    ("hands designed to break things", "hands built for hard labor"),
    ("heavy hands built for breaking", "oversized industrial hands"),
    ("hand clenched into fist at side", "hand resting at side"),
    ("hands clenched into fist", "hands resting"),
    ("intimidation", "imposing presence"),
    ("lethal purpose", "industrial purpose"),
    ("weaponized form", "utilitarian form"),
]

TARGET_SHOTS = {"EP001_SH17", "EP001_SH18"}


def apply_swaps(s: str) -> tuple[str, list[tuple[str, str]]]:
    """Return (new_string, list_of_(old,new)_applied)."""
    if not isinstance(s, str):
        return s, []
    applied = []
    out = s
    for old, new in SWAPS:
        if old in out:
            out = out.replace(old, new)
            applied.append((old, new))
    return out, applied


def walk_strings(d, path=""):
    """Yield (path, value, setter) for every string leaf in nested dict/list."""
    if isinstance(d, dict):
        for k, v in list(d.items()):
            yield from walk_strings(v, f"{path}.{k}" if path else k)
            if isinstance(v, str):
                yield (f"{path}.{k}" if path else k, v, lambda nv, _d=d, _k=k: _d.__setitem__(_k, nv))
    elif isinstance(d, list):
        for i, v in enumerate(d):
            yield from walk_strings(v, f"{path}[{i}]")
            if isinstance(v, str):
                yield (f"{path}[{i}]", v, lambda nv, _d=d, _i=i: _d.__setitem__(_i, nv))


def main():
    data = json.loads(PLAN.read_text())
    shots = data.get("shots") or data.get("shot_plans") or []

    changes = []
    for shot in shots:
        if shot.get("shot_id") not in TARGET_SHOTS:
            continue
        # Walk every string field in the shot dict
        for path, value, setter in walk_strings(shot):
            new_value, applied = apply_swaps(value)
            if applied:
                setter(new_value)
                changes.append({
                    "shot_id": shot["shot_id"],
                    "field": path,
                    "swaps": applied,
                })

    if not changes:
        print("No changes — plan already softened (idempotent no-op).")
        return 0

    bak = PLAN.with_suffix(f".json.bak.{int(time.time())}")
    shutil.copy2(PLAN, bak)
    print(f"Backup: {bak}")

    PLAN.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
    print(f"Applied {len(changes)} swaps across {len({c['shot_id'] for c in changes})} shots:")
    for c in changes:
        for old, new in c["swaps"]:
            print(f"  {c['shot_id']} {c['field']}: '{old}' → '{new}'")
    return 0


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