#!/usr/bin/env python3
"""Regenerate src/ratatoskr/web/static/persona_render_canon.json from the vendored
Worldtree d2 render canons (docs/vendor/worldtree-persona-canon/).

The web persona pane renders the CANONICAL affect->NL (mood word + relationship
directive) BYTE-EXACT to what Worldtree injects into the agent's context. That render
needs the relation canon parsed into per-band phrase maps; this script reparses the
vendored raw canons into the flat form the browser JS consumes.

Uses Worldtree's OWN loader (core.persona.stance_render.load_canon) as the authoritative
parser, so the flat form can never drift from Worldtree's parsing semantics. Requires
Worldtree's venv (pydantic etc.).

Run when scripts/canonical_drift.py flags a canon bump:
    PYTHONPATH=~/development/Worldtree ~/development/Worldtree/.venv/bin/python \
        scripts/build_persona_canon.py
"""
import json
from pathlib import Path

from core.persona.stance_render import load_canon  # Worldtree (authoritative parser)

ROOT = Path(__file__).resolve().parent.parent
VENDOR = ROOT / "docs" / "vendor" / "worldtree-persona-canon"
OUT = ROOT / "src" / "ratatoskr" / "web" / "static" / "persona_render_canon.json"

canon = load_canon(str(VENDOR / "d2-render-canon-v1.json"))
mood = json.loads((VENDOR / "d2-mood-render-canon-v1.json").read_text())
out = {
    "_source": "vendored from Worldtree core/persona/canon/{d2-mood-render-canon-v1,d2-render-canon-v1}.json",
    "_generated_by": "scripts/build_persona_canon.py (regen on canonical_drift flag)",
    "_render_path": "deterministic, no LLM; mirrors Worldtree describe_pad + render_d2_canonical byte-exact",
    "mood_grid": mood["describe_pad"]["valence_arousal_grid"],
    # Context-injection reconstruction (affect-egress-consumer-reference §2b/2c): the
    # hidden mood DIRECTIVE. occ_directives = per-OCC-type behavioral string + tier;
    # pad_band_fallback = the P×A-quadrant default when no emotion is salient. The
    # salience gate (emotion_salience) + full_only tiers drive which one fires — but
    # affect.emit is type-only (no intensity), so the consumer shows BOTH candidates.
    "mood_directive": {
        "salience": mood["thresholds"]["emotion_salience"],
        "pad_band_cutoff": mood["thresholds"]["pad_band_cutoff"],
        "full_only": mood["emotion_tiers"]["full_only"],
        "occ_directives": {
            t: {"directive": e["directive"], "tier": e["tier"]}
            for t, e in mood["derive_directive"]["occ_directives"].items()
        },
        "pad_band_fallback": mood["pad_band_fallback"],
    },
    "relation": {
        "trust_cuts": [list(c) for c in canon.trust_cuts],
        "warmth_cuts": [list(c) for c in canon.warmth_cuts],
        "agency_cuts": [list(c) for c in canon.agency_cuts],
        "warmth_phrase": canon.warmth_phrase, "warmth_beh": canon.warmth_beh,
        "agency_phrase": canon.agency_phrase, "agency_beh": canon.agency_beh,
        "history": canon.history,
        "prefix": "Use this graded relationship state: toward target, warmth is ",
        "tbeh": {"low_trust": "verify important claims before relying on them",
                 "cold_warmth": "protect boundaries while staying useful",
                 "default": "work from ordinary good faith"},
        "cold_warmth_bands": ["distant", "cold", "hostile"], "high_conf_floor": 0.55,
    },
}
OUT.write_text(json.dumps(out, indent=1) + "\n")
print(f"wrote {OUT.relative_to(ROOT)}")
