#!/usr/bin/env bash
# PreToolUse hook — defence-in-depth gate for property-preval's inputs.json.
#
# Fires on Write/Edit when the target path ends in `/preval/.../inputs.json`.
# Blocks (exit 2) before the payload reaches disk if any of the five named
# adherence violations is present. The render.py validator catches the same
# violations downstream — this hook stops the bad payload one step earlier,
# while the agent still has the opportunity to prompt the operator.
#
# Aborts named here mirror the AdherenceError causes raised by render.py:
#   - epc-certificate-not-supplied
#   - agent-listings-invented
#   - market-summary-invented
#   - brand-invented
#
# (cover-hero-not-subject is checked by render.py only — the slug heuristic
# needs the on-disk path to be inspectable, which isn't reliable from the
# tool args alone.)
#
# Error philosophy: fail closed on the four named contract violations; fail
# open on anything else (missing python3, malformed stdin, file not under
# preval/). Failing open lets unrelated tool calls proceed; the named
# violations are the only ones the hook owns.

set -u

if [ -t 0 ]; then
  exit 0
fi

if ! command -v python3 >/dev/null 2>&1; then
  cat >/dev/null
  echo "[preval-prompt-required] action=fail reason=python3-missing" >&2
  exit 0
fi

INPUT=$(cat)

INPUT="$INPUT" python3 <<'PY'
import json, os, sys

INPUT = os.environ.get("INPUT", "")

def passthrough(reason):
    print(f"[preval-prompt-required] action=passthrough reason={reason}", file=sys.stderr)
    sys.exit(0)

def block(cause, remediation):
    print(f"[preval-prompt-required] action=block cause={cause}", file=sys.stderr)
    print(f"[preval] {cause}: {remediation}", file=sys.stderr)
    sys.exit(2)

try:
    payload = json.loads(INPUT) if INPUT else {}
except Exception:
    passthrough("malformed-stdin")

tool_name = payload.get("tool_name") or ""
if tool_name not in ("Write", "Edit", "MultiEdit"):
    passthrough(f"tool={tool_name}")

tool_input = payload.get("tool_input") or {}
file_path = tool_input.get("file_path") or ""
norm = file_path.replace("\\", "/").lower()
if "/preval/" not in norm or not norm.endswith("/inputs.json"):
    passthrough("path-not-preval-inputs")

if tool_name == "Write":
    content = tool_input.get("content") or ""
elif tool_name == "Edit":
    content = tool_input.get("new_string") or ""
else:  # MultiEdit
    edits = tool_input.get("edits") or []
    content = edits[-1].get("new_string", "") if edits else ""

try:
    data = json.loads(content)
except Exception:
    passthrough("content-not-json")

if not isinstance(data, dict):
    passthrough("content-not-object")

overrides = data.get("agent_overrides") or {}

subject = data.get("subject") or {}
missing_epc = [
    f for f in ("total_floor_area_m2", "current_energy_rating", "lodgement_date")
    if not subject.get(f)
]
if missing_epc:
    block(
        "epc-certificate-not-supplied",
        f"subject is missing EPC certificate field(s) {missing_epc}. "
        "Paste total_floor_area_m2, current_energy_rating, and lodgement_date "
        "from the EPC certificate into subject before re-invoking. "
        "No mid-run prompt — the operator supplies the certificate before invocation.",
    )

if not overrides.get("listings_status_curated"):
    for listing in data.get("agent_listings") or []:
        origin = (listing or {}).get("origin")
        if not origin or origin == "operator":
            continue
        try:
            src_path = os.path.expanduser(str(origin))
            if not os.path.isfile(src_path):
                continue
            with open(src_path) as f:
                source = json.load(f)
        except Exception:
            continue
        if source.get("status") in (None, "") and listing.get("status"):
            block(
                "agent-listings-invented",
                f"agent_listings entry {listing.get('slug')} has status "
                f"'{listing['status']}' but source {src_path} carries status:null. "
                "Prompt the operator and set agent_overrides.listings_status_curated: true.",
            )

ms = data.get("market_summary") or {}
if ms.get("origin") not in ("operator", "operator-edited-llm-stub"):
    block(
        "market-summary-invented",
        "market_summary.origin must be 'operator' or 'operator-edited-llm-stub'. "
        "Have the operator draft or accept the paragraphs before writing inputs.json.",
    )

brand = data.get("brand") or {}
if brand.get("name") and not overrides.get("brand_selected_by_operator"):
    block(
        "brand-invented",
        f"brand.name ('{brand['name']}') was set without "
        "agent_overrides.brand_selected_by_operator: true. Ask the operator which "
        "estate agent is pitching; presence of a DESIGN.md on disk does not authorise selection.",
    )

print("[preval-prompt-required] action=allow", file=sys.stderr)
sys.exit(0)
PY
