#!/usr/bin/env python3
"""Classify CNS frame rects against the EXE surface-wrapper draw model."""
from __future__ import annotations

import json
from collections import Counter
from pathlib import Path
from typing import Any


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


def load_json(name: str) -> dict[str, Any]:
    return json.loads((OUT / name).read_text(encoding="utf-8"))


def rect_sizes(rects: list[dict[str, Any]]) -> list[dict[str, int]]:
    sizes = sorted(
        {
            (int(rect.get("w", 0)), int(rect.get("h", 0)))
            for rect in rects
            if int(rect.get("w", 0)) > 0 and int(rect.get("h", 0)) > 0
        }
    )
    return [{"w": w, "h": h} for w, h in sizes]


def compact_tables(row: dict[str, Any]) -> list[dict[str, Any]]:
    tables = []
    for table in row.get("tables") or []:
        tables.append(
            {
                "tableStartVaHex": table.get("tableStartVaHex"),
                "frameCount": table.get("frameCount"),
                "schema": table.get("schema"),
                "stride": table.get("stride"),
                "fieldLabel": table.get("fieldLabel"),
                "fieldPlus4Bound": table.get("fieldPlus4Bound"),
                "hasOffset2": table.get("hasOffset2"),
                "boundaryKind": table.get("boundaryKind"),
            }
        )
    return tables


def classify_row(precut: dict[str, Any], rect: dict[str, Any] | None) -> dict[str, Any]:
    mode = precut.get("mode")
    rects = (rect or {}).get("rects") or []
    sizes = rect_sizes(rects)
    variable_sizes = len(sizes) > 1
    descriptor = mode == "descriptor-plus4-source-rect-catalog"
    fixed_grid = mode == "fixed-grid-sheet"
    window_template = mode == "window-template-region-catalog"
    no_precut = mode == "no-precut-evidence"

    if descriptor:
        if variable_sizes or precut.get("hasOffset2"):
            wrapper_role = "surface-slot-plus-dispatch"
            conclusion = (
                "frame id must resolve through the descriptor +4 source-rect table; "
                "uniform wrapper cell width/height/columns cannot explain this asset alone"
            )
            confidence = "high"
            grid_sufficient = False
        else:
            wrapper_role = "surface-slot-with-uniform-looking-descriptor"
            conclusion = (
                "descriptor +4 table is still the grounded source; its rects happen to use one size"
            )
            confidence = "medium"
            grid_sufficient = False
    elif fixed_grid:
        wrapper_role = "grid-sheet-consumer"
        conclusion = "fixed grid sheet; wrapper-style cell width/height/columns is a compatible draw model"
        confidence = "high" if precut.get("status") == "confirmed-grid" else "medium"
        grid_sufficient = True
    elif window_template:
        wrapper_role = "window-template-system"
        conclusion = "window.cns uses reusable template/region records, not a descriptor +4 source-rect table"
        confidence = "high"
        grid_sufficient = False
    elif no_precut:
        wrapper_role = "not-bound-in-precut-catalog"
        conclusion = "no descriptor +4/fixed-grid binding is promoted for this asset"
        confidence = "medium"
        grid_sufficient = None
    else:
        wrapper_role = "unknown"
        conclusion = "unclassified row"
        confidence = "low"
        grid_sufficient = None

    source = (rect or {}).get("source") or {}
    table = source.get("table") or {}
    return {
        "asset": precut.get("asset"),
        "cns": precut.get("cns"),
        "category": precut.get("category"),
        "categoryLabel": precut.get("categoryLabel"),
        "mode": mode,
        "status": precut.get("status"),
        "frameCount": precut.get("frameCount"),
        "tableCount": precut.get("tableCount"),
        "bestTableStartVaHex": precut.get("bestTableStartVaHex"),
        "hasOffset2": precut.get("hasOffset2"),
        "rectReviewStatus": (rect or {}).get("status"),
        "rectSource": (rect or {}).get("selectedRectSource"),
        "rectCount": len(rects),
        "uniqueSizeCount": len(sizes),
        "uniqueSizes": sizes[:12],
        "variableSizes": variable_sizes,
        "wrapperRole": wrapper_role,
        "wrapperGridSufficient": grid_sufficient,
        "confidence": confidence,
        "conclusion": conclusion,
        "descriptorTables": compact_tables(precut),
        "rectTableFromReview": {
            "tableStartVaHex": table.get("tableStartVaHex"),
            "schema": table.get("schema"),
            "stride": table.get("stride"),
            "frameCount": table.get("frameCount"),
            "hasOffset2": table.get("hasOffset2"),
            "fieldLabel": table.get("fieldLabel"),
            "fieldPlus4Bound": table.get("fieldPlus4Bound"),
            "boundaryKind": table.get("boundaryKind"),
        }
        if table
        else None,
    }


def build_report() -> dict[str, Any]:
    precut = load_json("cns_precut_catalog_review.json")
    rect_review = load_json("cns_rect_review_data.json")
    rect_by_asset = {row.get("asset"): row for row in rect_review.get("rows") or []}
    rows = [classify_row(row, rect_by_asset.get(row.get("asset"))) for row in precut.get("rows") or []]

    mode_counts = Counter(row["mode"] for row in rows)
    role_counts = Counter(row["wrapperRole"] for row in rows)
    confidence_counts = Counter(row["confidence"] for row in rows)
    descriptor_rows = [row for row in rows if row["mode"] == "descriptor-plus4-source-rect-catalog"]
    variable_descriptor_rows = [row for row in descriptor_rows if row["variableSizes"] or row["hasOffset2"]]
    fixed_rows = [row for row in rows if row["mode"] == "fixed-grid-sheet"]
    joined = [row for row in rows if row["rectCount"]]

    return {
        "kind": "hwanse-surface-wrapper-catalog-review",
        "status": "wrapper-grid-vs-descriptor-catalog-classified",
        "source": [
            "out/cns_precut_catalog_review.json",
            "out/cns_rect_review_data.json",
            "out/render_pipeline_model_review.json",
            "docs/map3_active_object_overlay_notes.md",
            "docs/DIRECTDRAW_NOTES.md",
        ],
        "summary": {
            "surfaceWrapperTable": "0x0055abd8",
            "objectDrawSourceField": "object+0x28",
            "wrapperFieldOffsets": {
                "+0x2e": "cell/source width",
                "+0x30": "cell/source height",
                "+0x32": "columns",
                "+0x34": "group table candidate",
            },
            "assetRows": len(rows),
            "joinedRectRows": len(joined),
            "descriptorPlus4Assets": len(descriptor_rows),
            "descriptorPlus4VariableOrOffsetAssets": len(variable_descriptor_rows),
            "fixedGridAssets": len(fixed_rows),
            "windowTemplateAssets": mode_counts.get("window-template-region-catalog", 0),
            "noPrecutEvidenceAssets": mode_counts.get("no-precut-evidence", 0),
            "modeCounts": dict(mode_counts),
            "wrapperRoleCounts": dict(role_counts),
            "confidenceCounts": dict(confidence_counts),
            "wrapperGridFullyExplainsDescriptorCatalogs": False,
            "coreConclusion": (
                "The surface-wrapper path is grounded as a slot/grid draw mechanism, but most actor/monster/effect CNS assets "
                "are grounded by descriptor +4 source-rect catalogs.  The wrapper grid fields should not replace those "
                "variable source-rect tables."
            ),
        },
        "consumerEvidence": [
            {
                "vaHex": "0x004175d3",
                "meaning": "generic draw wrapper stores packed draw source into temporary object +0x28 and x/y into +0x1c/+0x20",
                "classification": "grounded-consumer",
            },
            {
                "vaHex": "0x0041747b",
                "meaning": "draw dispatcher extracts object+0x28 high word and indexes 0x0055abd8 surface wrapper table",
                "classification": "grounded-consumer",
            },
            {
                "vaHex": "0x004199a0",
                "meaning": "low-word frame/tile index is converted by wrapper +0x2e/+0x30/+0x32 in the uniform-grid path",
                "classification": "grounded-grid-converter",
            },
            {
                "vaHex": "0x00416ce2",
                "meaning": "active object path also consumes object+0x28 for source selector and surface slot",
                "classification": "grounded-active-object-consumer",
            },
        ],
        "modelBoundaries": [
            {
                "area": "fixed-grid sheets",
                "assets": [row["asset"] for row in fixed_rows],
                "conclusion": "cell-grid rendering is sufficient for these confirmed/review grid sheets",
            },
            {
                "area": "descriptor +4 catalogs",
                "assets": len(descriptor_rows),
                "conclusion": "source rects come from CNS descriptor +4-bound tables; many have variable sizes and/or offset2",
            },
            {
                "area": "window.cns",
                "assets": ["window"],
                "conclusion": "uses template/region records; keep separate from wrapper-grid and descriptor +4 models",
            },
            {
                "area": "full-screen/no-precut assets",
                "assets": [row["asset"] for row in rows if row["mode"] == "no-precut-evidence"],
                "conclusion": "not explained by promoted wrapper/precut evidence; do not fabricate rect catalogs",
            },
        ],
        "rows": rows,
        "nextFrontier": [
            {
                "area": "runtime wrapper memory dump",
                "reason": "static evidence proves the consumer fields, but not each loaded slot's live +0x2e/+0x30/+0x32/+0x34 values",
            },
            {
                "area": "object+0x2c / BltFast flags",
                "reason": "needed to separate source-color-key copy, opaque copy, draw priority, and palette/context behavior",
            },
            {
                "area": "descriptor table producer",
                "reason": "descriptor +4 tables are grounded; the loader that binds CNS descriptors to these tables remains useful for future automation",
            },
        ],
    }


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


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