"""Link existing variants to their parents: provenance.parent {id, version, release} + provenance.variant {kind, note}.

usage: python3 tools/backfill_lineage.py [--apply] [--report PATH]
Dry run by default. provenance is NOT in the release hash (lib/card.py RUNTIME), so cards are patched in place.
Only rules that are certain from the card data are applied; everything else is listed as ambiguous, never guessed.
  alternative  <slug>-g4 next to <slug>                      (same job, other author)
  alternative  <generator>-<slug> next to <slug> in vehicle.space (pixal3d-ab, pixal3d-batch, trellis2-fixed, trellis2-raw)
  retexture    a version that adds painted_glb (parent = the previous version of the same id)
  other        a lighter copy (lighter.from) or a game-weight re-export (feedback 'game weight')
  rerun        a later version whose provenance.feedback holds the operator's note
"""
import json, os, re, sys, glob
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, 'lib'))
import card as cardlib  # noqa: E402

GENS = ('pixal3d-ab', 'pixal3d-batch', 'trellis2-fixed', 'trellis2-raw')


def link(c, parent, kind, note):
    return {'parent': {'id': parent['id'], 'version': parent['version'], 'release': parent['release']},
            'variant': {'kind': kind, 'note': note}}


def classify(allc):
    """-> (links {(id, version): dict}, ambiguous [(id, version, why)])"""
    links, amb = {}, []
    by_slug = {}
    for cid in allc:
        by_slug.setdefault(cid.split('/')[1], []).append(cid)
    for cid, versions in sorted(allc.items()):
        slug = cid.split('/')[1]
        base = None
        if slug.endswith('-g4') and slug[:-3] in by_slug:
            base = ('g4', by_slug[slug[:-3]])
        for g in GENS:
            if slug.startswith(g + '-') and slug[len(g) + 1:] in by_slug:
                base = (g, by_slug[slug[len(g) + 1:]])
        if base:
            cands = base[1]
            if len(cands) != 1:
                amb.append((cid, None, f'several possible parents: {cands}'))
            else:
                p = cardlib.active(allc[cands[0]])
                note = f"same job, other author ({versions[0]['provenance'].get('author')}) vs {p['provenance'].get('author')}"
                for v in versions:
                    links[(cid, v['version'])] = link(v, p, 'alternative', note)
                continue
        elif re.match(r'(%s)-' % '|'.join(GENS), slug) or slug.endswith('-g4'):
            amb.append((cid, None, 'looks like an alternative author but no parent asset found'))
        if slug.startswith('room3-rerun2-'):
            amb.append((cid, None, 'photo re-run; the first room3 run is not in the library, no parent to link'))
        for prev, v in zip(versions, versions[1:]):
            if (cid, v['version']) in links:
                continue
            fb = v['provenance'].get('feedback') or ''
            if 'painted_glb' in v['files'] and 'painted_glb' not in prev['files']:
                links[(cid, v['version'])] = link(v, prev, 'retexture', 'painted copy (GPT Image 2 + forge-repaint), same mesh')
            elif v.get('lighter'):
                links[(cid, v['version'])] = link(v, cardlib.active([p for p in versions if p['version'] == v['lighter']['from']] or [prev]),
                                                  'other', f"lighter copy, mesh simplified {v['lighter'].get('saving_pct')}%")
            elif fb.startswith('game weight'):
                links[(cid, v['version'])] = link(v, prev, 'other', fb[:200])
            elif fb:
                links[(cid, v['version'])] = link(v, prev, 'rerun', fb[:200])
            elif v['status'] == 'legacy' and not v['provenance'].get('kit'):
                amb.append((cid, v['version'], 'later version with no feedback or marker (generator A/B output?)'))
            else:
                amb.append((cid, v['version'], 'later version with no feedback or marker'))
    return links, amb


def main():
    apply = '--apply' in sys.argv
    allc = cardlib.load_all(ROOT)
    links, amb = classify(allc)
    done = 0
    for (cid, ver), l in links.items():
        c = next(v for v in allc[cid] if v['version'] == ver)
        if c['provenance'].get('parent') == l['parent'] and c['provenance'].get('variant') == l['variant']:
            continue
        c['provenance'].update(l)
        if apply:
            cardlib.write(ROOT, c)
        done += 1
    kinds = {}
    for l in links.values():
        kinds[l['variant']['kind']] = kinds.get(l['variant']['kind'], 0) + 1
    print(f"{'applied' if apply else 'would patch'} {done} cards; linked {len(links)} {kinds}; ambiguous {len(amb)}")
    if '--report' in sys.argv:
        with open(sys.argv[sys.argv.index('--report') + 1], 'w') as f:
            json.dump({'linked': len(links), 'kinds': kinds, 'ambiguous': [{'id': i, 'version': v, 'why': w} for i, v, w in amb]}, f, indent=1)


if __name__ == '__main__':
    main()
