#!/usr/bin/env python3
"""Classify the remaining source frontier for status/comment selector byte +0.

The consumer side is now strong: after `40 1b 01 00`, `object+0xa8` points at
0x004576d8 and `40 14` reads `object+0xa8+0` to choose chapter/status-comment
groups.  The missing proof is not that consumer; it is the upstream writer of
the first byte in that backing block.

This report consolidates the adjacent evidence so later passes do not promote
the wrong thing:

* base bind routes are grounded, but they do not write the byte value;
* direct x86 absolute writes to +0 are absent;
* object-VM dynamic target +0 writers exist elsewhere, but not in status/menu;
* save/load restores the whole backing block and can populate +0 from disk, but
  is not a live scenario/chapter producer;
* selected-root selector bytes at +2/+3 belong to a different route layer.
"""
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 load_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 summary(name: str) -> dict[str, Any]:
    data = load_json(name)
    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 build() -> dict[str, Any]:
    backing_writer = summary("status_comment_backing_writer_scan.json")
    base_refs = summary("status_comment_backing_base_ref_review.json")
    base_route = summary("status_comment_base_bind_route_review.json")
    indirect = summary("status_comment_indirect_selector_writer_review.json")
    target0_context = summary("status_comment_target0_writer_context_review.json")
    meaning = summary("status_comment_selector_meaning_review.json")
    producer = summary("status_comment_producer_boundary_review.json")
    selected_root = summary("selected_root_live_writer_frontier_review.json")
    selected_scene = summary("selected_scene_text_root_consumer_review.json")
    global_mode = summary("global_mode_state_frontier_review.json")

    candidates = [
        {
            "candidate": "text/status consumer source",
            "status": "consumer-grounded-not-writer",
            "confidence": "high",
            "evidence": (
                f"40 1b mode1 route={base_route.get('textStatusOpcodeHex')} "
                f"payload uses={base_route.get('mode1PayloadUseCount')}; "
                f"selector source grounded={producer.get('selectorSourceGrounded')}"
            ),
            "decision": (
                "This proves where the value is read: object+0xa8+0 after base bind to 0x004576d8. "
                "It does not write the value."
            ),
        },
        {
            "candidate": "direct x86 store to 0x004576d8+0",
            "status": "not-found",
            "confidence": "high-negative",
            "evidence": (
                f"offset0 refs={backing_writer.get('offset0RefCount')}; "
                f"write-like={backing_writer.get('offset0WriteLikeCount')}; "
                f"direct writer proven={backing_writer.get('offset0WriterProven')}"
            ),
            "decision": "No absolute direct write to the selector byte is present in the current .text scan.",
        },
        {
            "candidate": "object-VM dynamic selector writers 0x8c/0x8d/0x8f",
            "status": "excluded-for-status-comment",
            "confidence": "high-negative",
            "evidence": (
                f"dynamic candidates={indirect.get('dynamicWriterCandidateCount')}; "
                f"target0={indirect.get('target0CandidateCount')}; "
                f"status-menu target0={indirect.get('target0InStatusMenuPayloadCount')}; "
                f"status-comment target0={indirect.get('target0InStatusCommentPayloadCount')}; "
                f"promoted target0 context={target0_context.get('statusCommentWriterPromoted')}"
            ),
            "decision": (
                "Target +0 dynamic writes are real in other active-object contexts, but their roots are field/battle object-local. "
                "They are not the status/comment story selector writer."
            ),
        },
        {
            "candidate": "persistent save/load block restore",
            "status": "can-populate-on-load-not-live-producer",
            "confidence": "high",
            "evidence": (
                f"block transfer refs={base_refs.get('blockTransferArgCount')}; "
                f"restore function={selected_scene.get('saveLoaderRestoreFunctionVaHex')}; "
                f"state block={selected_scene.get('saveLoaderRestoreStateBlockVaHex')} "
                f"size={selected_scene.get('saveLoaderRestoreStateBlockSizeHex')}; "
                f"route promoted={selected_scene.get('saveLoaderRestoreConsumerRouteProducerPromoted')}"
            ),
            "decision": (
                "0x00423319 loads/saves the 0x72-byte block, so +0 can come from persisted state after load. "
                "This is excluded as the live chapter/scenario writer."
            ),
        },
        {
            "candidate": "selected-root selector bytes +2/+3",
            "status": "different-route-layer",
            "confidence": "high",
            "evidence": (
                f"selector byte writer grounded={selected_root.get('selectorByteWriterGrounded')}; "
                f"selected-root writer grounded={selected_root.get('selectedRootWriterGrounded')}; "
                f"route producer promoted={selected_root.get('routeProducerPromoted')}"
            ),
            "decision": (
                "The +2/+3 selector bytes reconstruct selected roots. "
                "They are adjacent in the backing block but are not the +0 chapter/status-comment selector."
            ),
        },
        {
            "candidate": "cross-domain global mode/state frontier",
            "status": "no-live-producer-promoted",
            "confidence": "medium-high-negative",
            "evidence": (
                f"cross-domain functions={global_mode.get('crossDomainFunctionCount')}; "
                f"review required={global_mode.get('reviewRequiredCount')}; "
                f"promoted producers={global_mode.get('promotedProducerCount')}; "
                f"save/load bridge found={global_mode.get('saveLoadRestoreBridgeFound')}"
            ),
            "decision": (
                "Broad scene/menu/field/encounter intersections did not reveal a live writer. "
                "The strongest intersection remains save/load restore, already excluded as live producer."
            ),
        },
    ]

    excluded = [row for row in candidates if row["status"] not in {"consumer-grounded-not-writer"}]
    promoted = [
        row for row in candidates
        if row["status"] in {"live-writer-promoted", "writer-grounded"}
    ]

    next_frontier = [
        "Look for an indirect or block writer that populates 0x004576d8+0 without an absolute 0x004576d8 immediate.",
        "Prefer upstream scenario/chapter state transitions over status-menu payloads: current payloads only consume the value.",
        "If a future pass inspects 0x00423319 again, keep it classified as load/save restore unless it proves a live gameplay caller and value transition.",
        "A useful static target is code that writes a compact story/chapter byte and then later copies/serializes the 0x72 backing block.",
    ]

    return {
        "version": 1,
        "kind": "hwanse-status-comment-selector-value-source-frontier-review",
        "sourceArtifacts": [
            "out/status_comment_selector_meaning_review.json",
            "out/status_comment_base_bind_route_review.json",
            "out/status_comment_backing_writer_scan.json",
            "out/status_comment_indirect_selector_writer_review.json",
            "out/status_comment_target0_writer_context_review.json",
            "out/selected_root_live_writer_frontier_review.json",
            "out/selected_scene_text_root_consumer_review.json",
            "out/global_mode_state_frontier_review.json",
        ],
        "summary": {
            "selectorBackingBaseVaHex": "0x004576d8",
            "selectorOffsetHex": "0x00",
            "meaningCandidate": meaning.get("meaningCandidate", "story/chapter selector for status/menu context"),
            "consumerGrounded": bool(producer.get("selectorSourceGrounded")) and bool(base_route.get("statusCommentPayloadMode1UseFound")),
            "baseBindGrounded": base_refs.get("objectA8BackingBaseBindCount") == 2,
            "baseRouteGrounded": bool(base_route.get("statusCommentPayloadMode1UseFound")),
            "directWriterFound": bool(backing_writer.get("offset0WriterProven")),
            "dynamicStatusWriterFound": bool(indirect.get("statusCommentSelectorIndirectWriterProven")),
            "saveLoadCanPopulate": bool(base_refs.get("blockTransferArgCount")),
            "saveLoadPromotedAsLiveWriter": bool(selected_scene.get("saveLoaderRestoreConsumerRouteProducerPromoted")),
            "candidateCount": len(candidates),
            "promotedLiveWriterCount": len(promoted),
            "decision": (
                "The status/comment selector byte +0 is still writer-unresolved. "
                "Its consumer meaning is high-confidence story/chapter selection, but direct stores, local dynamic writers, "
                "selected-root primitives, and save/load restore do not prove a live writer."
            ),
        },
        "candidates": candidates,
        "excludedOrNonWriterCandidates": excluded,
        "nextFrontier": next_frontier,
    }


def render_html(data: dict[str, Any]) -> str:
    cards = "".join(
        f"<div class='card'><b>{h(k)}</b><span>{h(v)}</span></div>"
        for k, v in data["summary"].items()
    )
    candidate_rows = "".join(
        "<tr>"
        f"<td><b>{h(row['candidate'])}</b></td>"
        f"<td>{h(row['status'])}<br><small>{h(row['confidence'])}</small></td>"
        f"<td>{h(row['evidence'])}</td>"
        f"<td>{h(row['decision'])}</td>"
        "</tr>"
        for row in data["candidates"]
    )
    frontier = "".join(f"<li>{h(item)}</li>" for item in data["nextFrontier"])
    return f"""<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Status Comment Selector Value Source Frontier</title>
<style>
  :root {{ color-scheme: light dark; }}
  body {{ margin:24px; font-family:system-ui,-apple-system,Segoe UI,sans-serif; line-height:1.45; }}
  h1 {{ margin:0 0 8px; font-size:24px; }}
  .muted {{ color:#667085; }}
  .cards {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:10px; margin:16px 0; }}
  .card {{ border:1px solid #d0d5dd; border-radius:8px; padding:10px 12px; background:rgba(127,127,127,.05); }}
  .card b {{ display:block; color:#667085; font-size:12px; }}
  .card span {{ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; overflow-wrap:anywhere; }}
  .scroll {{ overflow-x:auto; }}
  table {{ width:100%; border-collapse:collapse; }}
  th,td {{ border:1px solid #d0d5dd; padding:7px 8px; vertical-align:top; }}
  th {{ text-align:left; background:rgba(127,127,127,.08); }}
  small {{ color:#667085; }}
  code {{ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }}
</style>
<h1>Status Comment Selector Value Source Frontier</h1>
<p class="muted"><code>0x004576d8+0</code> selector의 소비 증거와 writer 후보/배제 후보를 분리합니다.</p>
<div class="cards">{cards}</div>
<div class="scroll"><table>
<thead><tr><th>candidate</th><th>status</th><th>evidence</th><th>decision</th></tr></thead>
<tbody>{candidate_rows}</tbody>
</table></div>
<h2>Next Frontier</h2>
<ul>{frontier}</ul>
"""


def main() -> None:
    data = build()
    OUT.mkdir(exist_ok=True)
    WEB.mkdir(exist_ok=True)
    (OUT / "status_comment_selector_value_source_frontier_review.json").write_text(
        json.dumps(data, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    (WEB / "status_comment_selector_value_source_frontier_review.html").write_text(
        render_html(data),
        encoding="utf-8",
    )
    print(
        "wrote status comment selector value source frontier "
        f"(consumer={data['summary']['consumerGrounded']}, writer={data['summary']['promotedLiveWriterCount']})"
    )


if __name__ == "__main__":
    main()
