#!/usr/bin/env python3
"""Build a compact review for active-object trigger scripts.

The movement boundary review shows that core tile mutation does not directly
call encounter RNG or battle resources.  This follow-up checks the nearby
manual active-object bridge: object overlap can copy object +0xec into +0x40,
and opcode 0x5e can produce +0xec payloads.  Existing strict inventory shows
that those scripts currently decode as text/prompt or unknown scripts, with no
route or encounter proof.
"""
from __future__ import annotations

import html
import json
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
WEB = ROOT / "web"


def esc(value: Any) -> str:
    return html.escape("" if value is None else str(value), quote=True)


def read_json(path: Path, fallback: Any) -> Any:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return fallback


def write_json(path: Path, payload: Any) -> None:
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def top_unknown_scripts(inventory: dict[str, Any], limit: int = 20) -> list[dict[str, Any]]:
    rows = []
    for row in inventory.get("unknownScripts") or []:
        rows.append(
            {
                "scriptVaHex": row.get("scriptVaHex"),
                "decodedCommandCount": row.get("decodedCommandCount"),
                "classification": row.get("classification"),
                "firstOpcodes": [
                    item.get("opcodeHex")
                    for item in (row.get("firstCommands") or [])[:8]
                    if item.get("opcodeHex")
                ],
            }
        )
    return rows[:limit]


def build_report() -> dict[str, Any]:
    movement = read_json(OUT / "field_step_encounter_boundary_review.json", {})
    bridge = read_json(OUT / "manual_movement_trigger_bridge.json", {})
    producer = read_json(OUT / "object_script_payload_producer.json", {})
    inventory = read_json(OUT / "active_object_script_inventory.json", {})
    resource_bridge = read_json(OUT / "resource_command_vm_bridge_review.json", {})
    movement_summary = movement.get("summary") or {}
    bridge_summary = bridge.get("summary") or {}
    producer_summary = producer.get("summary") or {}
    inventory_summary = inventory.get("summary") or {}
    resource_summary = resource_bridge.get("summary") or {}
    route_proof_count = int(inventory_summary.get("routeProofScriptCount") or 0)
    map_operand_count = int(inventory_summary.get("mapCnsOperandCandidateScriptCount") or 0)
    active_route_rows = int(resource_summary.get("activeOpcode5eStrictRouteProofRows") or 0)
    return {
        "kind": "hwanse-field-active-object-trigger-boundary-review",
        "source": [
            "out/field_step_encounter_boundary_review.json",
            "out/manual_movement_trigger_bridge.json",
            "out/object_script_payload_producer.json",
            "out/active_object_script_inventory.json",
            "out/resource_command_vm_bridge_review.json",
            "tools/build_field_active_object_trigger_boundary_review.py",
        ],
        "summary": {
            "manualMovementToObjectScriptBridgeFound": bridge_summary.get("manualMovementToObjectScriptBridgeFound") is True,
            "objectEcProducerFound": producer_summary.get("objectEcProducerFound") is True,
            "strictInitializerCount": inventory_summary.get("strictInitializerCount"),
            "uniqueObjectEcScriptCount": inventory_summary.get("uniqueObjectEcScriptCount"),
            "textPromptScriptCount": inventory_summary.get("textPromptScriptCount"),
            "unknownScriptCount": inventory_summary.get("unknownScriptCount"),
            "routeProofScriptCount": route_proof_count,
            "mapCnsOperandCandidateScriptCount": map_operand_count,
            "resourceOpcode10FieldMapRefCount": resource_summary.get("fieldMapRefCount"),
            "activeOpcode5eStrictRouteProofRows": active_route_rows,
            "battleEntryConsumerPromoted": False,
            "fieldEncounterConsumerPromoted": False,
            "conclusion": (
                "manual overlap/object trigger bridge는 확정됐다. "
                "하지만 strict object +0xec script inventory에서는 route proof, map CNS operand, active opcode 0x5e route proof가 모두 0건이다. "
                "따라서 이 경로는 현재 프롬프트/상호작용 hotspot 근거로 보며, 필드 인카운트나 전투 진입 소비처로 승격하지 않는다."
            ),
        },
        "movementBoundary": {
            "coreMovementRng": movement_summary.get("sharedRngCallsInCoreMovementWindows"),
            "battleCnsRefs": movement_summary.get("battleBackgroundCnsRefsInKnownMovementWindows"),
            "monsterRefs": movement_summary.get("monsterOrBattleActorCnsRefsInKnownMovementWindows"),
        },
        "bridge": {
            "status": bridge_summary.get("manualMovementConsumerStatus"),
            "manualScriptOpcodeCount": bridge_summary.get("manualScriptOpcodeCount"),
            "routeProofFound": bridge_summary.get("routeProofFound"),
            "scope": bridge_summary.get("scope"),
        },
        "producer": {
            "producerOpcodeHex": producer_summary.get("producerOpcodeHex"),
            "handlerHex": "0x004079ff",
            "classification": producer_summary.get("classification"),
            "routePayloadProofFound": producer_summary.get("routePayloadProofFound"),
            "fieldMapTransitionPayloadFound": producer_summary.get("fieldMapTransitionPayloadFound"),
        },
        "inventoryClassifications": inventory_summary.get("classificationCounts") or {},
        "unknownScriptSamples": top_unknown_scripts(inventory),
        "nonClaims": [
            "active object +0xec bridge는 플레이어 수동 상호작용/겹침 스크립트 경로이지, 그 자체로 인카운트가 아니다.",
            "resource opcode 0x10은 맵 로드 기능을 증명하지만, 어떤 출구/인카운트가 그 패키지를 선택했는지는 별도 증거가 필요하다.",
            "unknown +0xec scripts는 route proof가 아니며, 후속 분석 대상일 뿐이다.",
        ],
    }


def render_html(report: dict[str, Any]) -> str:
    summary = report["summary"]
    cards = [
        ("+0xec bridge", summary["manualMovementToObjectScriptBridgeFound"]),
        ("+0xec producer", summary["objectEcProducerFound"]),
        ("strict inits", summary["strictInitializerCount"]),
        ("text scripts", summary["textPromptScriptCount"]),
        ("unknown scripts", summary["unknownScriptCount"]),
        ("route proof", summary["routeProofScriptCount"]),
    ]
    card_html = "".join(
        f"<div class='card'><b>{esc(label)}</b><span>{esc(value)}</span></div>" for label, value in cards
    )
    class_rows = "".join(
        f"<tr><td>{esc(k)}</td><td>{esc(v)}</td></tr>"
        for k, v in sorted((report.get("inventoryClassifications") or {}).items())
    )
    unknown_rows = "".join(
        "<tr>"
        f"<td><code>{esc(row.get('scriptVaHex'))}</code></td>"
        f"<td>{esc(row.get('decodedCommandCount'))}</td>"
        f"<td>{esc(', '.join(row.get('firstOpcodes') or []))}</td>"
        "</tr>"
        for row in report.get("unknownScriptSamples") or []
    )
    non_claims = "".join(f"<li>{esc(item)}</li>" for item in report.get("nonClaims") or [])
    payload = json.dumps(report, ensure_ascii=False)
    return f"""<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Field Active Object Trigger Boundary Review</title>
  <style>
    body {{ margin:0; font-family: system-ui, sans-serif; background:#101318; color:#edf1f7; }}
    main {{ max-width:1180px; margin:0 auto; padding:24px; }}
    a {{ color:#8ecbff; }}
    .nav {{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:16px; }}
    .chip {{ border:1px solid #334155; border-radius:999px; padding:6px 10px; text-decoration:none; background:#161b22; }}
    .summary {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin:16px 0; }}
    .card {{ border:1px solid #2b3544; border-radius:8px; padding:12px; background:#161b22; }}
    .card b {{ display:block; color:#9fb1c9; font-size:12px; text-transform:uppercase; letter-spacing:.04em; }}
    .card span {{ display:block; margin-top:8px; font-size:22px; }}
    section {{ border:1px solid #273244; border-radius:10px; padding:16px; margin:16px 0; background:#141922; }}
    table {{ border-collapse:collapse; width:100%; font-size:13px; }}
    th, td {{ border-bottom:1px solid #283342; padding:8px; text-align:left; }}
    th {{ color:#b9c7dc; background:#111722; }}
    code {{ color:#dbeafe; }}
    pre {{ white-space:pre-wrap; background:#0b0f14; border:1px solid #253044; border-radius:8px; padding:12px; max-height:360px; overflow:auto; }}
  </style>
</head>
<body>
<main>
  <div class="nav">
    <a class="chip" href="index.html">index</a>
    <a class="chip" href="field_step_encounter_boundary_review.html">step boundary</a>
    <a class="chip" href="field_encounter_static_review.html">field encounter static</a>
    <a class="chip" href="resource_reference_review.html">resource reference</a>
  </div>
  <h1>Field Active Object Trigger Boundary Review</h1>
  <p>{esc(summary["conclusion"])}</p>
  <div class="summary">{card_html}</div>
  <section>
    <h2>Classification Counts</h2>
    <table><thead><tr><th>classification</th><th>count</th></tr></thead><tbody>{class_rows}</tbody></table>
  </section>
  <section>
    <h2>Unknown +0xec Script Samples</h2>
    <p>route proof가 아니라 후속 후보로 남긴 샘플입니다.</p>
    <table><thead><tr><th>script</th><th>commands</th><th>first opcodes</th></tr></thead><tbody>{unknown_rows}</tbody></table>
  </section>
  <section>
    <h2>Non Claims</h2>
    <ul>{non_claims}</ul>
  </section>
  <section>
    <h2>Raw JSON</h2>
    <pre id="json"></pre>
  </section>
</main>
<script>
window.HWANSE_FIELD_ACTIVE_OBJECT_TRIGGER_BOUNDARY_REVIEW_READY = true;
window.HWANSE_FIELD_ACTIVE_OBJECT_TRIGGER_BOUNDARY_REVIEW = {payload};
document.getElementById('json').textContent = JSON.stringify(window.HWANSE_FIELD_ACTIVE_OBJECT_TRIGGER_BOUNDARY_REVIEW, null, 2);
</script>
</body>
</html>
"""


def main() -> int:
    report = build_report()
    write_json(OUT / "field_active_object_trigger_boundary_review.json", report)
    html_text = render_html(report)
    (WEB / "field_active_object_trigger_boundary_review.html").write_text(html_text, encoding="utf-8")
    print("field_active_object_trigger_boundary_review ok")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
