#!/usr/bin/env python3
"""Summarize non-palette motion producer evidence for map tiles and battle effects."""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any


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


def read_json(name: str) -> dict[str, Any]:
    path = OUT / name
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return {}
    return data if isinstance(data, dict) else {}


def pick(data: dict[str, Any], *keys: str, default: Any = None) -> Any:
    node: Any = data
    for key in keys:
        if not isinstance(node, dict):
            return default
        node = node.get(key)
    return default if node is None else node


def build_report() -> dict[str, Any]:
    palette = read_json("palette_pipeline_review.json")
    map_boundary = read_json("map_animation_execution_boundary_review.json")
    live_buffer = read_json("map_animation_live_buffer_writer_review.json")
    frame_script = read_json("map_animation_frame_script_tile_write_review.json")
    active_script = read_json("map_animation_active_object_script_tile_write_review.json")
    non_object = read_json("map_animation_non_object_tile_write_root_review.json")
    draw_transform = read_json("map_animation_draw_tile_transform_review.json")
    tick_route = read_json("map_animation_tick_route_review.json")
    specialized_loop = read_json("map_animation_specialized_loop_boundary_review.json")
    helper_motion = read_json("battle_helper_position_motion_review.json")
    effect_pattern = read_json("battle_effect_animation_pattern_review.json")
    visual_assertion = read_json("battle_effect_visual_assertion_review.json")
    motion_boundary = read_json("battle_motion_boundary_loop_review.json")

    map_surfaces = [
        {
            "area": "palette ramp/copy",
            "source": "palette_pipeline_review.json",
            "status": palette.get("status"),
            "finding": pick(palette, "summary", "coreConclusion"),
            "promotion": "promoted-color-only",
            "boundary": "색상/fade/ramp 근거이며 타일 위치/source-index motion 근거는 아니다.",
        },
        {
            "area": "live layer0/layer1 buffer writers",
            "source": "map_animation_live_buffer_writer_review.json",
            "status": live_buffer.get("status"),
            "finding": pick(live_buffer, "summary", "decision"),
            "promotion": "handlers-grounded-root-unbound",
            "boundary": "opcode 0x58/0x6b/0x76 live tile writer는 확인됐지만 animated map root/tick와 연결되지 않았다.",
        },
        {
            "area": "draw-time tile transform",
            "source": "map_animation_draw_tile_transform_review.json",
            "status": draw_transform.get("status"),
            "finding": pick(draw_transform, "summary", "decision"),
            "promotion": "negative-for-visible-motion",
            "boundary": "현재 draw path는 live layer0 tile id를 고정 grid 공식으로 source rect에 매핑한다.",
        },
        {
            "area": "field tick route",
            "source": "map_animation_tick_route_review.json",
            "status": tick_route.get("status"),
            "finding": pick(tick_route, "summary", "decision"),
            "promotion": "tick-route-grounded-producer-indirect",
            "boundary": "per-frame route는 확인됐지만 직접 live map buffer를 mutate하지 않는다.",
        },
        {
            "area": "strict active-object +0xec scripts",
            "source": "map_animation_active_object_script_tile_write_review.json",
            "status": active_script.get("status"),
            "finding": pick(active_script, "summary", "decision"),
            "promotion": "negative",
            "boundary": "strict object script command boundary에서는 live tile write가 나오지 않는다.",
        },
        {
            "area": "runtime-installed object +0x64 frame scripts",
            "source": "map_animation_frame_script_tile_write_review.json",
            "status": frame_script.get("status"),
            "finding": pick(frame_script, "summary", "decision"),
            "promotion": "negative",
            "boundary": "opcode 0x20 attach target과 0x18 state-table entry 모두 command-boundary tile write가 없다.",
        },
        {
            "area": "non-object aligned tile-write roots",
            "source": "map_animation_non_object_tile_write_root_review.json",
            "status": non_object.get("status"),
            "finding": pick(non_object, "summary", "decision"),
            "promotion": "roots-exist-no-animated-binding",
            "boundary": "aligned tile-write commands는 있지만 animated map/root + animated coordinate/tile을 동시에 만족하지 않는다.",
        },
        {
            "area": "specialized loop scan",
            "source": "map_animation_specialized_loop_boundary_review.json",
            "status": specialized_loop.get("status"),
            "finding": pick(specialized_loop, "summary", "decision"),
            "promotion": "negative",
            "boundary": "tick/redraw/draw 주변 direct callee에서도 별도 live-buffer/tile-shift producer를 찾지 못했다.",
        },
    ]

    battle_surfaces = [
        {
            "area": "helper position/motion fields",
            "source": "battle_helper_position_motion_review.json",
            "status": helper_motion.get("status"),
            "finding": (
                f"{pick(helper_motion, 'summary', 'helpersWithPositionOrMotion', default=0)} helpers carry position/motion; "
                f"{pick(helper_motion, 'summary', 'helpersWithMotionStep', default=0)} carry motion step."
            ),
            "promotion": "promoted-helper-motion-semantics",
            "boundary": "전투 helper/object motion은 팔레트가 아니라 object position/motion field 기반으로 해석 가능하다.",
        },
        {
            "area": "effect pattern join",
            "source": "battle_effect_animation_pattern_review.json",
            "status": effect_pattern.get("status"),
            "finding": (
                f"{pick(effect_pattern, 'summary', 'skillsWithHelpers', default=0)} skill rows have helpers; "
                f"{pick(effect_pattern, 'summary', 'skillsWithExecutionRequirements', default=0)} rows require spawn/motion execution."
            ),
            "promotion": "joined-by-skill",
            "boundary": "스킬별 helper/effect frame 결합은 됐지만 원작 픽셀 오라클은 별도 검증이다.",
        },
        {
            "area": "visual execution assertions",
            "source": "battle_effect_visual_assertion_review.json",
            "status": visual_assertion.get("status"),
            "finding": (
                f"{pick(visual_assertion, 'summary', 'requirementRowsWithColoredPreview', default=0)} requirement rows have colored previews; "
                f"pixel oracle status is {pick(visual_assertion, 'summary', 'pixelOracleStatus')}."
            ),
            "promotion": "web-runner-asserted-not-original-oracle",
            "boundary": "웹 runner 표현은 검증됐지만 원작 런타임 캡처와 동일하다고 주장하지 않는다.",
        },
        {
            "area": "motion boundary loops",
            "source": "battle_motion_boundary_loop_review.json",
            "status": motion_boundary.get("status"),
            "finding": (
                f"{pick(motion_boundary, 'summary', 'motionBoundaryRows', default=0)} boundary rows, "
                f"helpers {pick(motion_boundary, 'summary', 'helpers', default=[])}."
            ),
            "promotion": "promoted-loop-semantics",
            "boundary": "일부 화면 경계/loop형 motion은 정적 분석으로 의미가 확정됐다.",
        },
    ]

    map_summary = {
        "animatedMapCount": pick(map_boundary, "summary", "animatedMapCount", default=0),
        "animatedCellCount": pick(map_boundary, "summary", "animatedCellCount", default=0),
        "liveBufferCandidateVmTileWriteRefCount": pick(map_boundary, "summary", "liveBufferCandidateVmTileWriteRefCount", default=0),
        "liveBufferAnimatedRootBindingCount": pick(map_boundary, "summary", "liveBufferAnimatedRootBindingCount", default=0),
        "frameScriptTileWriteProducerFound": pick(map_boundary, "summary", "frameScriptTileWriteProducerFound", default=False),
        "nonObjectPromotedAnimatedProducerCommandCount": pick(map_boundary, "summary", "nonObjectPromotedAnimatedProducerCommandCount", default=0),
        "specializedLoopStrongCandidateCount": pick(map_boundary, "summary", "specializedLoopStrongCandidateCount", default=0),
        "drawTimeVisibleMotionTransformFound": pick(map_boundary, "summary", "drawTimeVisibleMotionTransformFound", default=False),
    }
    battle_summary = {
        "helpersWithPositionOrMotion": pick(helper_motion, "summary", "helpersWithPositionOrMotion", default=0),
        "helpersWithMotionStep": pick(helper_motion, "summary", "helpersWithMotionStep", default=0),
        "skillsWithHelpers": pick(effect_pattern, "summary", "skillsWithHelpers", default=0),
        "skillsWithEffectFrames": pick(effect_pattern, "summary", "skillsWithEffectFrames", default=0),
        "skillsWithExecutionRequirements": pick(effect_pattern, "summary", "skillsWithExecutionRequirements", default=0),
        "motionModelClassifiedRows": pick(visual_assertion, "summary", "motionModelClassifiedRows", default=0),
        "pixelOracleStatus": pick(visual_assertion, "summary", "pixelOracleStatus"),
    }

    return {
        "kind": "hwanse-non-palette-motion-producer-review",
        "status": "battle-motion-grounded-map-tile-motion-producer-unbound",
        "source": [
            "out/palette_pipeline_review.json",
            "out/map_animation_execution_boundary_review.json",
            "out/map_animation_live_buffer_writer_review.json",
            "out/map_animation_frame_script_tile_write_review.json",
            "out/map_animation_non_object_tile_write_root_review.json",
            "out/battle_helper_position_motion_review.json",
            "out/battle_effect_animation_pattern_review.json",
            "out/battle_effect_visual_assertion_review.json",
        ],
        "summary": {
            "mapConclusion": (
                "Map-side non-palette visible tile motion is still unbound: live tile writer handlers exist, "
                "but no static path currently ties them to animated map roots/cells or the field tick route."
            ),
            "battleConclusion": (
                "Battle-side non-palette effect motion is substantially grounded through helper position/motion fields, "
                "spawn streams, and visual runner assertions, with the explicit boundary that it is not yet an original runtime pixel oracle."
            ),
            "mapSummary": map_summary,
            "battleSummary": battle_summary,
            "recommendedNextStaticStep": (
                "For map motion, inspect the animated-map load/update dispatcher or any caller that materializes opcode 0x58/0x6b/0x76 streams "
                "after resource load; do not continue from palette handlers or strict object frame scripts unless new refs appear."
            ),
        },
        "mapSurfaces": map_surfaces,
        "battleSurfaces": battle_surfaces,
        "promotionDecisions": [
            {
                "area": "field map palette effects",
                "decision": "promote as color/ramp only",
                "reason": "palette buffer/copy/SetEntries path is grounded, but it cannot by itself move waterfall/fire source tiles.",
            },
            {
                "area": "field map visible tile motion",
                "decision": "keep unpromoted",
                "reason": "no static producer writes animated-map live layer0 source ids under a proven per-map/per-tick binding.",
            },
            {
                "area": "battle effect helper motion",
                "decision": "promote for runner use",
                "reason": "helper position/motion opcode fields and spawn/motion execution requirements are joined by skill and checked by visual assertions.",
            },
        ],
        "nextFrontier": [
            "Search upstream materializers for tile-write command streams instead of re-scanning decoded +0xec/+0x64 object scripts.",
            "Separate map tile source-index motion from palette-only effects in every future map animation claim.",
            "If static search remains saturated, use a narrow runtime watchpoint on 0x00595af0 live layer0 entries for map1_01a/map1_02b animated cells.",
            "For battle effects, future work should target original-runtime pixel/oracle comparison rather than reclassifying already joined helper motion rows.",
        ],
    }


def main() -> int:
    report = build_report()
    OUT.mkdir(exist_ok=True)
    (OUT / "non_palette_motion_producer_review.json").write_text(
        json.dumps(report, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    return 0


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