#!/usr/bin/env python3
"""Recover a sub-record's FULL schema field order by merging every unit .tbl.

Each .tbl's string pool lists only the fields that .tbl declares, in schema
order. Merging them is a topological sort over the pairwise "k[i] precedes
k[i+1]" constraints of all 110 tables: any single table is a subsequence of the
schema, so the union order is the schema order — provided the constraints are
acyclic, which is itself the check that the premise holds.

Usage: schema_order.py <raw-token-dump> <SubRecord> [solved.txt]
"""
import sys
from collections import defaultdict

sys.path.insert(0, ".")
from order_check import section, keys_in_order, solved_offsets  # noqa: E402

SUBS = {"Generic", "Maneuver", "Shield", "Explosion", "Mass", "Effect", "SE"}


def merged_order(dump, sub):
    tbls = [l.split()[1] for l in open(dump) if l.startswith("RAW ")]
    succ, indeg, nodes = defaultdict(set), defaultdict(int), []
    seqs = []
    for t in tbls:
        ks = keys_in_order(section(dump, t, sub, SUBS))
        if ks:
            seqs.append(ks)
        for k in ks:
            if k not in nodes:
                nodes.append(k)
        for a, b in zip(ks, ks[1:]):
            if b not in succ[a]:
                succ[a].add(b)
                indeg[b] += 1
    # Kahn, breaking ties by first-seen order so the result is deterministic
    ready = [n for n in nodes if indeg[n] == 0]
    out, cycles = [], False
    while ready:
        ready.sort(key=nodes.index)
        n = ready.pop(0)
        out.append(n)
        for m in sorted(succ[n], key=nodes.index):
            indeg[m] -= 1
            if indeg[m] == 0:
                ready.append(m)
    if len(out) != len(nodes):
        cycles = True
        out += [n for n in nodes if n not in out]
    return out, seqs, cycles


def main():
    dump, sub = sys.argv[1], sys.argv[2]
    order, seqs, cycles = merged_order(dump, sub)
    print(f"# {sub}: {len(order)} fields merged from {len(seqs)} tables"
          f"{'  !! CYCLE — order is not consistent' if cycles else ''}")
    # every table must be a subsequence of the merged order — the real check
    pos = {k: i for i, k in enumerate(order)}
    bad = [s for s in seqs if [pos[k] for k in s] != sorted(pos[k] for k in s)]
    print(f"# tables that are NOT a subsequence of the merged order: {len(bad)}")

    if len(sys.argv) > 3:
        solved = solved_offsets(sys.argv[3])
        anchors = [(i, k, solved[k]) for i, k in enumerate(order) if k in solved]
        votes = defaultdict(list)
        for i, k, (off, *_) in anchors:
            votes[off - 4 * i].append((i, k))
        print(f"# {len(anchors)} solved anchors; base votes:")
        for b, ks in sorted(votes.items(), key=lambda kv: -len(kv[1])):
            idx = sorted(i for i, _ in ks)
            print(f"#   base {b:#07x}: {len(ks):3d} anchors, indices {idx[0]}..{idx[-1]}")
        for i, k in enumerate(order):
            s = solved.get(k)
            note = f"solver={s[0]:#x} {s[1]} agree={s[2]} dist={s[3]}" if s else ""
            print(f"  [{i:3d}] {k:<34} {note}")


if __name__ == "__main__":
    main()
