#!/usr/bin/env python3
"""Summarize the current static boundary for field encounter producers.

This report intentionally does not add a new encounter heuristic.  It collects
the already-separated evidence for the walking path, RNG selector opcode,
battle actor formation consumer, and resource descriptors so the next analysis
pass starts from the remaining producer gap instead of repeating dead ends.
"""
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 read_json(name: str) -> dict[str, Any]:
    try:
        data = json.loads((OUT / name).read_text(encoding="utf-8"))
    except FileNotFoundError:
        return {}
    return data if isinstance(data, dict) else {}


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


def summary(data: dict[str, Any]) -> dict[str, Any]:
    value = data.get("summary")
    return value if isinstance(value, dict) else {}


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


def status_label(ok: bool) -> str:
    return "confirmed" if ok else "not promoted"


def build_report() -> dict[str, Any]:
    static = read_json("field_encounter_static_review.json")
    static_summary = summary(static)
    step = read_json("field_step_encounter_boundary_review.json")
    step_summary = summary(step)
    opcode1c = read_json("field_rng_selector_opcode1c_review.json")
    opcode1c_summary = summary(opcode1c)
    formation = read_json("field_encounter_formation_boundary_review.json")
    formation_summary = summary(formation)
    tick_route = read_json("field_encounter_tick_route_boundary_review.json")
    tick_route_summary = summary(tick_route)
    formation_context = read_json("field_encounter_formation_context_review.json")
    formation_context_summary = summary(formation_context)
    active_object = read_json("field_active_object_trigger_boundary_review.json")
    active_object_summary = summary(active_object)

    surfaces = [
        {
            "surface": "field movement / collision",
            "artifact": "field_step_encounter_boundary_review.json",
            "promoted": False,
            "grounded": bool(step_summary.get("movementConsumerGrounded")),
            "finding": (
                "actor controller와 collision helper까지는 확정됐지만 core movement 창에는 "
                f"shared RNG {step_summary.get('sharedRngCallsInCoreMovementWindows', 0)}건, "
                f"battle CNS {step_summary.get('battleBackgroundCnsRefsInKnownMovementWindows', 0)}건, "
                f"monster CNS {step_summary.get('monsterOrBattleActorCnsRefsInKnownMovementWindows', 0)}건이다."
            ),
            "nextEvidence": "이동 좌표 갱신 이후 별도 field tick/event consumer가 battle setup을 호출하는 증거.",
        },
        {
            "surface": "field tick/update route",
            "artifact": "field_encounter_tick_route_boundary_review.json",
            "promoted": False,
            "grounded": True,
            "finding": (
                f"known field windows {tick_route_summary.get('knownFieldWindowCount', 0)}개 안에서 "
                f"key direct calls {tick_route_summary.get('keyDirectCallsInsideKnownFieldWindows', 0)}개, "
                f"promotable key calls {tick_route_summary.get('promotableKeyDirectCallsInsideKnownFieldWindows', 0)}개다. "
                f"shared RNG inside field windows {tick_route_summary.get('sharedRngCallsInsideKnownFieldWindows', 0)}개는 "
                f"encounter class {tick_route_summary.get('sharedRngEncounterClassInsideKnownFieldWindows', 0)}개다. "
                f"formation stream exact refs {tick_route_summary.get('formationCandidateExactPointerRefCount', 0)}개."
            ),
            "nextEvidence": "known field tick 밖의 higher-level mode/state dispatcher에서 step success 이후 battle setup으로 이어지는 증거.",
        },
        {
            "surface": "shared RNG call classification",
            "artifact": "field_encounter_static_review.json",
            "promoted": False,
            "grounded": True,
            "finding": (
                f"shared RNG call site {static_summary.get('rngCallSiteCount', 0)}개는 대부분 전투 계산, "
                "레벨업, scene random gate 등으로 분리됐다. "
                f"nearby CNS가 붙은 RNG는 {static_summary.get('rngCallSitesWithNearbyCnsCount', 0)}개이고 "
                f"direct field encounter evidence는 {static_summary.get('rngDirectFieldEncounterEvidenceCount', 0)}개다."
            ),
            "nextEvidence": "지도/걸음/인카운트 카운터와 함께 쓰이는 아직 미분류 RNG call 또는 RNG 이전 producer.",
        },
        {
            "surface": "opcode 0x1c random selector",
            "artifact": "field_rng_selector_opcode1c_review.json",
            "promoted": False,
            "grounded": bool(opcode1c_summary.get("handlerEntryMatchesExpected")),
            "finding": (
                "handler table entry와 RNG selector primitive는 확인됐다. 다만 well-formed command block은 "
                f"{opcode1c_summary.get('wellFormedOpcode1cBlockCount', 0)}개이고, "
                f"현재 refs {opcode1c_summary.get('invalidLowByteReferenceCount', 0)}개는 모두 low-byte pointer artifact다."
            ),
            "nextEvidence": "실행 가능한 command stream 안에서 opcode 0x1c가 ctx+0x40 branch를 고르는 사례.",
        },
        {
            "surface": "opcode 0x1e battle actor formation",
            "artifact": "field_encounter_formation_boundary_review.json",
            "promoted": False,
            "grounded": bool(formation_summary.get("formationConsumerPromoted")),
            "finding": (
                "opcode 0x1e / 0x0040c084는 enemy actor formation consumer로 확정됐다. "
                f"selector roots {formation_summary.get('selectorRootCount', 0)}개 중 "
                f"map+monster root는 {formation_summary.get('selectorMapMonsterRootCount', 0)}개, "
                f"all-three root는 {formation_summary.get('selectorAllThreeRootCount', 0)}개다."
            ),
            "nextEvidence": "field/map producer가 0x1e stream 또는 equivalent battle setup stream을 선택하는 포인터/분기.",
        },
        {
            "surface": "0x1e formation byte candidates",
            "artifact": "field_encounter_formation_context_review.json",
            "promoted": False,
            "grounded": False,
            "finding": (
                f"byte-shaped candidate {formation_context_summary.get('candidateCount', 0)}개는 pointer refs "
                f"{formation_context_summary.get('referencedCandidateCount', 0)}개, scene hits "
                f"{formation_context_summary.get('sceneContextHitCandidateCount', 0)}개, next valid opcode "
                f"{formation_context_summary.get('nextNonDefaultOpcodeCandidateCount', 0)}개다. "
                "따라서 현재는 search anchor일 뿐이다."
            ),
            "nextEvidence": "candidate 주변이 실제 VM stream임을 보이는 producer/root 또는 valid opcode chain.",
        },
        {
            "surface": "battle/background resource descriptors",
            "artifact": "field_encounter_static_review.json",
            "promoted": False,
            "grounded": bool(static_summary.get("battleBackgroundDescriptorsFound")),
            "finding": (
                "btl resource descriptor와 enemy sprite descriptor는 리소스 로딩 근거다. "
                f"resourceDescriptorsProveBattleEntry={static_summary.get('resourceDescriptorsProveBattleEntry')}."
            ),
            "nextEvidence": "리소스 묶음이 실제 battle-entry/event setup으로 소비되는 caller/root.",
        },
        {
            "surface": "manual active-object trigger bridge",
            "artifact": "field_active_object_trigger_boundary_review.json",
            "promoted": False,
            "grounded": bool(active_object_summary.get("manualMovementToObjectScriptBridgeFound")),
            "finding": (
                "manual overlap/object +0xec bridge와 object script producer는 확인됐다. "
                f"strict initializer {active_object_summary.get('strictInitializerCount', 0)}개 중 "
                f"route proof {active_object_summary.get('routeProofScriptCount', 0)}개, "
                f"map CNS operand {active_object_summary.get('mapCnsOperandCandidateScriptCount', 0)}개, "
                f"active 0x5e route proof {active_object_summary.get('activeOpcode5eStrictRouteProofRows', 0)}개다."
            ),
            "nextEvidence": "수동 상호작용 bridge가 아니라 field tick/encounter check root에서 battle setup으로 이어지는 증거.",
        },
    ]

    promoted = [row for row in surfaces if row["promoted"]]
    grounded = [row for row in surfaces if row["grounded"]]
    return {
        "kind": "hwanse-field-encounter-producer-boundary-review",
        "status": "producer-not-promoted",
        "source": [
            "out/field_encounter_static_review.json",
            "out/field_step_encounter_boundary_review.json",
            "out/field_rng_selector_opcode1c_review.json",
            "out/field_encounter_formation_boundary_review.json",
            "out/field_encounter_tick_route_boundary_review.json",
            "out/field_encounter_formation_context_review.json",
            "out/field_active_object_trigger_boundary_review.json",
            "tools/build_field_encounter_producer_boundary_review.py",
        ],
        "summary": {
            "surfaceCount": len(surfaces),
            "groundedConsumerOrPrimitiveCount": len(grounded),
            "promotedProducerSurfaceCount": len(promoted),
            "fieldEncounterProducerPromoted": False,
            "fieldTickRouteProducerPromoted": tick_route_summary.get("fieldTickRouteProducerPromoted", False),
            "decision": (
                "필드 인카운트에 필요한 일부 consumer/primitive는 확정됐지만, "
                "걸음/지도 상태에서 전투 배경과 몬스터 편성을 선택하는 producer/root는 아직 정적으로 승격되지 않았다."
            ),
        },
        "surfaces": surfaces,
        "nextFrontier": [
            "active object callback 밖의 field/event tick 루프에서 battle setup 호출자를 찾는다.",
            "opcode 0x1c/0x1e가 같은 executable command stream에 들어가는 새 root boundary를 찾는다.",
            "map family/resource descriptor와 monster formation을 한 root가 함께 참조하는지 재검토한다.",
            "정적 higher-level mode/state dispatcher에서 walking step 완료와 battle setup을 연결하는 분기를 찾는다.",
        ],
    }


def render_html(report: dict[str, Any]) -> str:
    summary = report["summary"]
    cards = [
        ("status", report["status"]),
        ("surfaces", summary["surfaceCount"]),
        ("grounded", summary["groundedConsumerOrPrimitiveCount"]),
        ("producer", status_label(summary["fieldEncounterProducerPromoted"])),
    ]
    card_html = "".join(f"<div class='card'><b>{h(k)}</b><span>{h(v)}</span></div>" for k, v in cards)
    rows = []
    for row in report["surfaces"]:
        rows.append(
            "<tr>"
            f"<td><b>{h(row['surface'])}</b><br><code>{h(row['artifact'])}</code></td>"
            f"<td>{h('grounded' if row['grounded'] else 'candidate')}</td>"
            f"<td>{h(status_label(row['promoted']))}</td>"
            f"<td>{h(row['finding'])}</td>"
            f"<td>{h(row['nextEvidence'])}</td>"
            "</tr>"
        )
    frontier = "".join(f"<li>{h(item)}</li>" for item in report["nextFrontier"])
    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 Encounter Producer Boundary Review</title>
  <style>
    body {{ margin:0; background:#101318; color:#edf1f7; font-family:system-ui,sans-serif; }}
    main {{ max-width:1240px; 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(170px,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; }}
    .card span {{ display:block; margin-top:8px; font-size:18px; overflow-wrap:anywhere; }}
    section {{ border:1px solid #273244; border-radius:10px; padding:16px; margin:16px 0; background:#141922; overflow:auto; }}
    table {{ border-collapse:collapse; width:100%; min-width:1080px; font-size:13px; }}
    th,td {{ border-bottom:1px solid #283342; padding:8px; text-align:left; vertical-align:top; }}
    th {{ color:#b9c7dc; background:#111722; }}
    code {{ color:#dbeafe; overflow-wrap:anywhere; }}
    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_encounter_static_review.html">field encounter static</a>
    <a class="chip" href="field_step_encounter_boundary_review.html">step boundary</a>
    <a class="chip" href="field_rng_selector_opcode1c_review.html">opcode 0x1c</a>
    <a class="chip" href="field_encounter_formation_context_review.html">formation context</a>
  </div>
  <h1>Field Encounter Producer Boundary Review</h1>
  <p>{h(summary["decision"])}</p>
  <div class="summary">{card_html}</div>
  <section>
    <h2>Producer Boundary</h2>
    <table>
      <thead><tr><th>surface</th><th>grounding</th><th>producer</th><th>finding</th><th>next evidence</th></tr></thead>
      <tbody>{''.join(rows)}</tbody>
    </table>
  </section>
  <section>
    <h2>Next Frontier</h2>
    <ul>{frontier}</ul>
  </section>
  <section>
    <h2>Raw JSON</h2>
    <pre id="json"></pre>
  </section>
</main>
<script>
window.HWANSE_FIELD_ENCOUNTER_PRODUCER_BOUNDARY_REVIEW = {payload};
document.getElementById('json').textContent = JSON.stringify(window.HWANSE_FIELD_ENCOUNTER_PRODUCER_BOUNDARY_REVIEW, null, 2);
</script>
</body>
</html>
"""


def main() -> int:
    report = build_report()
    write_json(OUT / "field_encounter_producer_boundary_review.json", report)
    html_text = render_html(report)
    (WEB / "field_encounter_producer_boundary_review.html").write_text(html_text, encoding="utf-8")
    print("field_encounter_producer_boundary_review ok")
    print(json.dumps(report["summary"], ensure_ascii=False, indent=2))
    return 0


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