"""Pin the token enum, so the three-token pattern can be read rather than guessed.

Query 1 established the shapes but not the vocabulary:
  FUN_180162880 FutCreatePack        3 tokenizer calls, then a key loop, exits on token 10
  FUN_18014cc60 FutCreateUser        3 tokenizer calls, then a key loop
  FUN_180124ee0 /purchased root      TWO tokenizer calls, then FUN_18013bd40, no loop at all
That two-versus-three difference is the whole answer, but only if we know what a token IS.
Note it already refutes the claim in plan-2026-08-05-pack-opening.md section 2 that the
/purchased root "spends the same three tokens". It spends two.

FUN_1801c7f10 is only a pushback wrapper: it returns param_1[0x35] if a token was pushed
back, else classifies one byte via FUN_1801c67a0. So FUN_1801c67a0 holds the enum.
Known so far, from usage rather than from the enum: 10 ends the key loop, 0xd ends an array
(`while (iVar7 != 0xd)` around the itemList element parser), 8 is set when the input is
exhausted cleanly, 1 on error, 7 on a first-call special case.

WHAT WOULD FALSIFY THE DESCEND READING: if token 2 in the /purchased root is a
START_ARRAY rather than a key or a START_OBJECT, then `itemData` is not being consumed as a
wrapper and the two-call pattern means something else entirely.
"""
import traceback

TARGETS = [
    (0x1801C67A0, "token CLASSIFIER -- the enum lives here"),
    (0x18013BD40, "/purchased body sub-parser (what the 2-token root hands off to)"),
    (0x180141EE0, "key reader used by the CreatePack/CreateUser loops"),
    (0x1801C63E0, "parser init (called before begin-object in every root)"),
]


def dump(va, title):
    try:
        f = func(va)
        src = dec(va)
        n = f.getBody().getNumAddresses() if f else -1
        print("\n" + "=" * 78)
        print("%#x  %s" % (va, title))
        print("body %d bytes / decompile %d chars  (PRINTED IN FULL)" % (n, len(src)))
        print("=" * 78)
        print(src)
    except Exception:
        print("!! failed on %#x" % va)
        traceback.print_exc()


try:
    for va, name in TARGETS:
        dump(va, name)

    # Cross-check the two-versus-three count mechanically over every response root we can
    # name, rather than trusting three hand-picked examples. A root is recognised by calling
    # the parser-init, begin-object and the tokenizer.
    print("\n" + "=" * 78)
    print("TOKEN CALLS BEFORE THE FIRST KEY READ, across all begin-object callers")
    print("=" * 78)
    roots = sorted({e for _, _, _, e in xrefs_to(0x1801C8270) if e})
    print("begin-object callers: %d" % len(roots))
    for ent in roots:
        try:
            f = func(ent)
            if f is None:
                continue
            # order the call sites by address and count tokenizer calls that precede the
            # first key-reader call, which is what "descend depth" actually means here
            toks, keys, begin = [], [], []
            for ad in f.getBody().getAddresses(True):
                ins = listing.getInstructionAt(ad)
                if ins is None:
                    continue
                for r in ins.getReferencesFrom():
                    t = int(r.getToAddress().getOffset())
                    a = int(ad.getOffset())
                    if t == 0x1801C7F10:
                        toks.append(a)
                    elif t in (0x180141EE0,):
                        keys.append(a)
                    elif t == 0x1801C8270:
                        begin.append(a)
            if not begin:
                continue
            b = min(begin)
            first_key = min(keys) if keys else None
            pre = [a for a in toks if a > b and (first_key is None or a < first_key)]
            print("  %#x  %-22s begin@%#x  tokens_before_first_key=%d  keyreads=%d"
                  % (ent, f.getName(), b, len(pre), len(keys)))
        except Exception:
            print("  %#x  <failed>" % ent)
            traceback.print_exc()

except Exception:
    traceback.print_exc()
