"""Extract the level's 2D colliders — the ones a Rigidbody2D player actually hits.

For a game whose player is a Rigidbody2D with BoxCollider2D/CircleCollider2D (check the player prefab),
and Unity's 2D physics only ever collides 2D colliders with 2D colliders. The 3D MeshColliders on
the art are for the land-check SphereCast and shadows; nothing a ship can collide with lives there.
So the playable walls are the small, designer-authored set of BoxCollider2D / CircleCollider2D
components on the level prefabs — no depth, no slicing, no guessing.

Reuses unity_extract's transform composition (nested prefabs, overrides, layer overrides).
"""
import json, os, re, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import unity_extract as ue

SOLID_LAYERS = {0, 10}     # Physics2D matrix: Players collide with Default and Terrain; not Shadows(18)
out = []

def vec2(body, key, default):
    m = re.search(re.escape(key) + r': \{x: ([-\d.eE+]+), y: ([-\d.eE+]+)\}', body)
    return (float(m.group(1)), float(m.group(2))) if m else default

def walk(path, docs, tid, world, depth, mods, tag):
    if depth > ue.MAX_DEPTH: return
    cls, body = docs[tid]
    if cls == '1001':
        prefab(path, docs, tid, body, world, depth, tag, mods); return
    if 'm_LocalPosition' not in body: return
    trs = ue.local_trs(body); mod = mods.get(tid)
    if mod: trs = ue.apply_mods(trs, mod)
    w = ue.compose(world, trs)
    goid = ue.fid(body, 'm_GameObject')
    if not ue.go_active(docs, goid, mods): return
    name = ue.go_name(docs, goid); layer = ue.go_layer(docs, goid, mods)
    gd = docs.get(goid)
    if gd:
        (p, q, s) = w
        zrot = ue.quat_euler_z(q)
        for m in re.finditer(r'- component: \{fileID: (\d+)\}', gd[1]):
            cd = docs.get(m.group(1))
            if not cd or cd[0] not in ('61', '58'): continue
            cb = cd[1]
            if not ue.comp_enabled(cb, m.group(1), mods): continue
            trig = re.search(r'm_IsTrigger: (\d)', cb); trig = bool(trig and trig.group(1) == '1')
            off = vec2(cb, 'm_Offset', (0.0, 0.0))
            wo = ue.qrot(q, (off[0] * s[0], off[1] * s[1], 0.0))
            cx, cy = p[0] + wo[0], p[1] + wo[1]
            rec = dict(name=name, tag=tag or 'scene', layer=layer, trigger=trig, cx=round(cx, 4), cy=round(cy, 4), z=round(p[2], 3))
            if cd[0] == '61':
                size = vec2(cb, 'm_Size', (1.0, 1.0))
                rec.update(kind='box', hw=round(abs(size[0] * s[0]) / 2, 4), hh=round(abs(size[1] * s[1]) / 2, 4), rot=round(zrot, 3))
            else:
                r = re.search(r'm_Radius: ([-\d.eE+]+)', cb)
                rec.update(kind='circle', r=round(float(r.group(1)) * max(abs(s[0]), abs(s[1])), 4) if r else 0.5)
            out.append(rec)
    seg = body[body.find('m_Children:'):body.find('m_Father:')] if 'm_Children:' in body and 'm_Father:' in body else ''
    for m in re.finditer(r'- \{fileID: (\d+)\}', seg):
        if m.group(1) in docs: walk(path, docs, m.group(1), w, depth + 1, mods, tag)

def prefab(path, docs, tid, body, world, depth, tag, outer_mods=None):
    m = re.search(r'm_SourcePrefab: \{fileID: \d+, guid: ([0-9a-f]{32})', body)
    src = ue.guid2path.get(m.group(1)) if m else None
    if not src or not os.path.exists(src): return
    pdocs = ue.parse_file(src)
    modmap = ue.instance_mods(body, docs, tid, pdocs, outer_mods)
    root = next((pid for pid, (c, b) in pdocs.items() if c == '4' and ue.fid(b, 'm_Father') == '0'), None)
    if root is not None and not ue.go_active(pdocs, ue.fid(pdocs[root][1], 'm_GameObject'), modmap):
        return   # the instance switches the whole prefab off (a scene-level m_IsActive override)
    name = os.path.basename(src).replace('.prefab', '')
    if depth == 0:
        # scene-level instance: honour level patches (its own placement is the root override)
        root = next((pid for pid, (c, b) in pdocs.items() if c == '4' and ue.fid(b, 'm_Father') == '0'), None)
        rp = ue.apply_mods(ue.local_trs(pdocs[root][1]), modmap.get(root, {})) if root else None
        wp = ue.compose(world, rp)[0] if rp else world[0]
        if ue.patch_skips_instance(name, wp): return
    expand(src, pdocs, world, depth + 1, modmap, tag or name)

def expand(path, docs, world, depth, mods, tag):
    if depth > ue.MAX_DEPTH: return
    for tid, (cls, body) in docs.items():
        if cls == '4' and ue.fid(body, 'm_Father') == '0' and 'm_LocalPosition' in body:
            walk(path, docs, tid, world, depth, mods, tag)
    for tid, (cls, body) in docs.items():
        if cls != '1001': continue
        chain = []; cur = ue.fid(body, 'm_TransformParent'); guard = 0
        while cur != '0' and cur in docs and guard < 40:
            ccls, cbody = docs[cur]
            if ccls != '4' or 'm_LocalPosition' not in cbody: break
            chain.append((cur, cbody)); cur = ue.fid(cbody, 'm_Father'); guard += 1
        w = world
        for cid, cbody in reversed(chain):
            trs = ue.local_trs(cbody); mm = mods.get(cid)
            if mm: trs = ue.apply_mods(trs, mm)
            w = ue.compose(w, trs)
        prefab(path, docs, tid, body, w, depth, tag, mods)

def add_patched_instances(scene_docs):
    """Place the extra instances a level patch adds, using the scene's own copy of that prefab."""
    for tid, (cls, body) in scene_docs.items():
        if cls != '1001': continue
        m = re.search(r'm_SourcePrefab: \{fileID: \d+, guid: ([0-9a-f]{32})', body)
        src = ue.guid2path.get(m.group(1)) if m else None
        if not src: continue
        name = os.path.basename(src).replace('.prefab', '')
        adds = ue.patch_added_instances(name)
        if not adds: continue
        pdocs = ue.parse_file(src)
        root = next((pid for pid, (c, b) in pdocs.items() if c == '4' and ue.fid(b, 'm_Father') == '0'), None)
        if root is None: continue
        for (pos, quat) in adds:
            # the added instance's root gets the patched placement instead of the scene's override
            modmap = {root: {'m_LocalPosition.x': pos[0], 'm_LocalPosition.y': pos[1], 'm_LocalPosition.z': pos[2],
                             'm_LocalRotation.x': quat[0], 'm_LocalRotation.y': quat[1], 'm_LocalRotation.z': quat[2], 'm_LocalRotation.w': quat[3]}}
            expand(src, pdocs, ((0.0, 0.0, 0.75), (0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0)), 1, modmap, name + ' (patch)')
        break   # one source prefab is enough

if __name__ == '__main__':
    scene = sys.argv[1] if len(sys.argv) > 1 else ue.scene_path_from_config()
    docs = ue.parse_file(scene)
    expand(scene, docs, ((0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0), (1.0, 1.0, 1.0)), 0, {}, None)
    add_patched_instances(docs)
    # dedupe identical records reached by two paths
    seen = set(); uniq = []
    for r in out:
        k = json.dumps({k: v for k, v in r.items() if k not in ('name',)}, sort_keys=True)
        if k in seen: continue
        seen.add(k); uniq.append(r)
    print(json.dumps(uniq))
