#!/usr/bin/env python3
"""Apply/analyse an exported RC50 visual review patch.

Default is report-only. It does not overwrite runtime sequences. It can optionally write a
review proposal JSON that groups the user's flags into action buckets for the next RC.
"""
from __future__ import annotations
import argparse, json
from datetime import datetime, timezone
from pathlib import Path
from collections import Counter, defaultdict

ROOT = Path(__file__).resolve().parents[1]
DATA = ROOT / 'assets' / 'data' / 'image-admin'

def load(path: Path):
    with open(path, 'r', encoding='utf-8') as f: return json.load(f)

def dump(path: Path, data):
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2); f.write('\n')

ACTION_MAP = {
    'ok': 'retain-current-frame',
    'wrong-foot-hand': 'inspect-limb-binding-or-replace-candidate',
    'wrong-direction': 'inspect-facing-flip-path-direction',
    'wrong-anchor': 'inspect-anchor-landmark-or-path-lock',
    'wrong-image-candidate': 'rematch-against-blueprint-candidates',
    'needs-new-pose': 'create-or-source-new-pose-candidate',
    'hold': 'keep-runtime-hold-until-reviewed',
    'replace-exercise': 'replace-exercise-in-program-or-blueprint',
}

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('patch', type=Path, help='rc50_visual_review_patch.json')
    ap.add_argument('--write-proposal', action='store_true', help='Write assets/data/image-admin/rc50_visual_review_proposal.json')
    ap.add_argument('--out', type=Path, default=None)
    args = ap.parse_args()
    patch = load(args.patch)
    if patch.get('schema') != 'rc50-visual-review-patch-v1':
        raise SystemExit(f"Unexpected schema: {patch.get('schema')}")
    manifest = load(DATA / 'rc50_visual_review_manifest.json')
    ex_map = {e['exerciseKey']: e for e in manifest.get('exercises', [])}
    flags = patch.get('flags', [])
    by_status = Counter(f.get('status','unknown') for f in flags)
    by_ex = defaultdict(list)
    missing = []
    for f in flags:
        if f.get('exerciseKey') not in ex_map:
            missing.append(f)
        by_ex[f.get('exerciseKey')].append(f)
    proposal = {
        'schema': 'rc50-visual-review-proposal-v1',
        'createdAt': datetime.now(timezone.utc).isoformat(timespec='seconds'),
        'sourcePatchCreatedAt': patch.get('createdAt'),
        'runtimeChanged': False,
        'summary': {
            'flagCount': len(flags),
            'missingExerciseFlags': len(missing),
            'statusCounts': dict(by_status),
            'exercisesFlagged': len(by_ex),
            'blockingNextRc': sum(1 for f in flags if f.get('status') in ('wrong-foot-hand','wrong-direction','wrong-anchor','wrong-image-candidate','needs-new-pose','hold','replace-exercise')),
        },
        'exerciseActions': [],
        'missingExerciseFlags': missing,
        'policy': 'Review proposal only. Human/AI next step may edit sequences in a later RC, but this script does not change runtime data.',
    }
    for ex_key, items in sorted(by_ex.items()):
        ex = ex_map.get(ex_key, {})
        proposal['exerciseActions'].append({
            'exerciseKey': ex_key,
            'label': ex.get('label'),
            'runtimeGate': ex.get('runtimeGate'),
            'flagCount': len(items),
            'statusCounts': dict(Counter(i.get('status') for i in items)),
            'recommendedActions': sorted({ACTION_MAP.get(i.get('status'), 'inspect') for i in items}),
            'flags': items,
        })
    out = args.out or (DATA / 'rc50_visual_review_proposal.json')
    if args.write_proposal:
        dump(out, proposal)
    print(json.dumps(proposal['summary'], indent=2, ensure_ascii=False))
    if args.write_proposal:
        print(out)

if __name__ == '__main__': main()
