#!/usr/bin/env python3
"""Generate the SAND trampler/walker blueprint hashes FROM SCRATCH (no harvesting needed).

RE'd from GameAssembly.dll (2026-06-15 build):
  MD5Utility.ComputeHash(obj) = MD5( UTF8( JsonConvert.SerializeObject(obj) ) ), formatted as
  uppercase hex with no separators (each byte -> ToString("X2")).  [ghidra: MD5Utility$$ObjectToByteArray,
  $$ComputeHash]

The three top-level WalkerBlueprintDto hashes are MD5 of the *compact* JSON (Newtonsoft default:
no whitespace, PascalCase keys in C# declaration order, nulls included, enums as STRING names,
CellCoordinate as {x,y,z}) of:
  CompartmentsHash = ComputeHash(blueprint.Compartments)   [List<CompartmentBlueprintDto>]   VERIFIED
  ConnectionsHash  = ComputeHash(blueprint.Connections)    [List<ConnectionBlueprintDto>]    VERIFIED  <- "hash #3"
  DefinitionsHash  = ComputeHash(<the CompartmentDefinitionDto list for the compartments>)    PROVISIONAL
                     (hashes the actual definitions, not blueprint-internal fields; verify live
                      against a fresh GetTrampler + GetCompartmentDefinitions once unfrozen.)

To match Newtonsoft byte-for-byte, build the dicts with keys already in declaration order and
serialize with json.dumps(obj, separators=(",",":"), ensure_ascii=False). Do NOT sort keys.
"""
import json, hashlib


def md5_hash(obj) -> str:
    """MD5Utility.ComputeHash(obj): MD5 of compact-JSON(obj), uppercase hex."""
    blob = json.dumps(obj, separators=(",", ":"), ensure_ascii=False)
    return hashlib.md5(blob.encode("utf-8")).hexdigest().upper()


def compartments_hash(compartments: list) -> str:
    """#1 CompartmentsHash = MD5(JSON(Compartments list)). VERIFIED."""
    return md5_hash(compartments)


def connections_hash(connections: list) -> str:
    """#3 ConnectionsHash = MD5(JSON(Connections list)). VERIFIED.

    Each connection must serialize as (declaration order):
      {"Id": int, "EpbId": str|None, "ActualDirection": {"x":,"y":,"z":},
       "GridCoordinate": {"x":,"y":,"z":}, "SlotType": "<name>", "State": "<name>"}
    """
    return md5_hash(connections)


def definitions_hash(definitions: list) -> str:
    """#2 DefinitionsHash = MD5(JSON(<CompartmentDefinitionDto list>)). PROVISIONAL — verify live."""
    return md5_hash(definitions)


def apply_hashes(blueprint: dict, definitions: list = None) -> dict:
    """Set the three top-level hashes on a blueprint dict in place. Returns it.
    definitions: ordered CompartmentDefinitionDto list for #2 (optional until verified)."""
    blueprint["CompartmentsHash"] = compartments_hash(blueprint["Compartments"])
    blueprint["ConnectionsHash"] = connections_hash(blueprint["Connections"])
    if definitions is not None:
        blueprint["DefinitionsHash"] = definitions_hash(definitions)
    return blueprint


def _selftest():
    import os
    p = os.path.join(os.path.dirname(__file__), "..", "extracted",
                     "playfab_titledata_TramplerBlueprint3.json")
    b = json.load(open(p))
    c1 = compartments_hash(b["Compartments"])
    c3 = connections_hash(b["Connections"])
    assert c1 == b["CompartmentsHash"], "CompartmentsHash %s != %s" % (c1, b["CompartmentsHash"])
    assert c3 == b["ConnectionsHash"], "ConnectionsHash %s != %s" % (c3, b["ConnectionsHash"])
    print("selftest OK:")
    print("  CompartmentsHash #1:", c1, "== stored")
    print("  ConnectionsHash  #3:", c3, "== stored   <- generatable from scratch")
    print("  DefinitionsHash  #2: provisional (verify live vs current GetTrampler).")


if __name__ == "__main__":
    _selftest()
