#!/usr/bin/env python3
import json, os, datetime
from collections import defaultdict

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
IN_PATH = os.path.join(ROOT, 'qa', 'mesh_nex_v88_mediapipe', 'V88_MEDIAPIPE_EXTRACT_AND_GATE_REPORT.json')
OUT_DIR = os.path.join(ROOT, 'qa', 'mesh_nex_v89_exercise_aware')
OUT_JSON = os.path.join(OUT_DIR, 'V89_EXERCISE_AWARE_GATE_REPORT.json')
OUT_JS_JSON = os.path.join(ROOT, 'assets', 'js', 'data', 'nex-stage2-phase3-v89-exercise-aware-gate.json')
OUT_JS = os.path.join(ROOT, 'assets', 'js', 'data', 'nex-stage2-phase3-v89-exercise-aware-gate.js')
os.makedirs(OUT_DIR, exist_ok=True)
os.makedirs(os.path.dirname(OUT_JS_JSON), exist_ok=True)

with open(IN_PATH) as f:
    data = json.load(f)

REQUIRED_FULL_BODY = ['nose','left_shoulder','right_shoulder','left_hip','right_hip','left_knee','right_knee','left_ankle','right_ankle']
OPTIONAL_ARM = {'left_elbow','right_elbow','left_wrist','right_wrist'}
SEQUENCE_RUNTIME_REVIEW = {'back_pedal_to_sprint','rebound_side_toss'}
CARRY_RUNTIME_REVIEW = {'suitcase_carry'}
LOWER_BODY_ARM_OPTIONAL = {'ascending_skater_jumps','decel_step_down','goblet_sumo_squat'}
HYPEREXTENSION = {'hyperextension'}

rows_out=[]
exercise_summary=defaultdict(lambda: {'posePassRows':0,'poseReviewRows':0,'poseFixRows':0,'runtimeReviewRows':0,'rows':[]})

for row in data['rows']:
    vis = {lm['name']: float(lm.get('visibility',0.0) or 0.0) for lm in row.get('landmarks',[])}
    required_min = min(vis.get(n,0.0) for n in REQUIRED_FULL_BODY) if vis else 0.0
    low = set((row.get('candidateMetrics') or {}).get('lowCriticalLandmarks') or [])
    verylow = set((row.get('candidateMetrics') or {}).get('veryLowCriticalLandmarks') or [])
    ex = row['exerciseKey']
    pose_status = 'POSE_FIX_BEFORE_SOURCE_AUTHORITY'
    pose_reason = 'default'
    runtime_status = 'NO_EXTRA_RUNTIME_REVIEW'

    if row.get('mediaPipeGateStatus') == 'PASS_MEDIAPIPE_SOURCE_CANDIDATE':
        pose_status = 'POSE_SOURCE_PASS'
        pose_reason = 'already_pass_v88'
    elif ex in LOWER_BODY_ARM_OPTIONAL:
        only_optional_low = (low | verylow).issubset(OPTIONAL_ARM)
        if required_min >= 0.80 and only_optional_low:
            pose_status = 'POSE_SOURCE_PASS'
            pose_reason = 'lower_body_profile_far_arm_optional'
        elif required_min >= 0.60:
            pose_status = 'POSE_REVIEW'
            pose_reason = 'lower_body_profile_review'
    elif ex in HYPEREXTENSION:
        only_optional_or_knee_low = (low | verylow).issubset(OPTIONAL_ARM | {'right_knee','left_knee'})
        if required_min >= 0.80 and only_optional_or_knee_low:
            pose_status = 'POSE_SOURCE_PASS'
            pose_reason = 'hyperextension_profile_support_machine_optional_arms'
        elif required_min >= 0.45:
            pose_status = 'POSE_REVIEW'
            pose_reason = 'hyperextension_profile_review'
    elif ex in SEQUENCE_RUNTIME_REVIEW:
        if required_min >= 0.80:
            pose_status = 'POSE_SOURCE_PASS'
            pose_reason = 'sequence_drill_pose_pass_runtime_separate'
        else:
            pose_status = 'POSE_REVIEW'
            pose_reason = 'sequence_drill_pose_review'
        runtime_status = 'RUNTIME_SEQUENCE_REVIEW_REQUIRED'
    elif ex in CARRY_RUNTIME_REVIEW:
        if required_min >= 0.80:
            pose_status = 'POSE_SOURCE_PASS'
            pose_reason = 'carry_pose_pass_gait_separate'
        else:
            pose_status = 'POSE_REVIEW'
            pose_reason = 'carry_pose_review'
        runtime_status = 'RUNTIME_GAIT_REVIEW_REQUIRED'
    else:
        if required_min >= 0.80:
            pose_status = 'POSE_SOURCE_PASS'
            pose_reason = 'full_body_required_pass'
        elif required_min >= 0.60:
            pose_status = 'POSE_REVIEW'
            pose_reason = 'full_body_required_review'

    # ensure runtime review remains for sequence/carry even if V88 pass
    if ex in SEQUENCE_RUNTIME_REVIEW and runtime_status == 'NO_EXTRA_RUNTIME_REVIEW':
        runtime_status = 'RUNTIME_SEQUENCE_REVIEW_REQUIRED'
    if ex in CARRY_RUNTIME_REVIEW and runtime_status == 'NO_EXTRA_RUNTIME_REVIEW':
        runtime_status = 'RUNTIME_GAIT_REVIEW_REQUIRED'

    out = dict(row)
    out['exerciseAwareProfile'] = (
        'lower_body_arm_optional' if ex in LOWER_BODY_ARM_OPTIONAL else
        'sequence_runtime_review' if ex in SEQUENCE_RUNTIME_REVIEW else
        'carry_runtime_review' if ex in CARRY_RUNTIME_REVIEW else
        'hyperextension_support' if ex in HYPEREXTENSION else
        'default_full_body'
    )
    out['exerciseAwareRequiredLandmarks'] = REQUIRED_FULL_BODY
    out['exerciseAwareRequiredMinVisibility'] = round(required_min, 6)
    out['poseSourceStatusV89'] = pose_status
    out['poseSourceReasonV89'] = pose_reason
    out['runtimeReviewStatusV89'] = runtime_status
    rows_out.append(out)

    s=exercise_summary[ex]
    if pose_status=='POSE_SOURCE_PASS': s['posePassRows']+=1
    elif pose_status=='POSE_REVIEW': s['poseReviewRows']+=1
    else: s['poseFixRows']+=1
    if runtime_status!='NO_EXTRA_RUNTIME_REVIEW': s['runtimeReviewRows']+=1
    s['rows'].append({
        'phaseId': row['phaseId'],
        'poseSourceStatusV89': pose_status,
        'runtimeReviewStatusV89': runtime_status,
        'requiredMinVisibility': round(required_min,6),
        'originalV88GateStatus': row.get('mediaPipeGateStatus')
    })

exercise_records=[]
for ex, s in sorted(exercise_summary.items()):
    if s['posePassRows'] == len(s['rows']):
        overall_pose='POSE_SOURCE_READY_ALL_ROWS'
    elif s['posePassRows']>=1 and s['poseFixRows']==0:
        overall_pose='POSE_SOURCE_PARTIAL_READY'
    elif s['posePassRows']>=1:
        overall_pose='POSE_SOURCE_MIXED'
    elif s['poseReviewRows']>=1:
        overall_pose='POSE_SOURCE_REVIEW_ONLY'
    else:
        overall_pose='POSE_SOURCE_NOT_READY'
    runtime='RUNTIME_REVIEW_REQUIRED' if s['runtimeReviewRows'] else 'NO_EXTRA_RUNTIME_REVIEW'
    exercise_records.append({
        'exerciseKey': ex,
        'posePassRows': s['posePassRows'],
        'poseReviewRows': s['poseReviewRows'],
        'poseFixRows': s['poseFixRows'],
        'runtimeReviewRows': s['runtimeReviewRows'],
        'overallPoseSourceStatusV89': overall_pose,
        'overallRuntimeStatusV89': runtime,
        'rows': s['rows']
    })

summary={
    'schema':'NEXT11_V89_EXERCISE_AWARE_GATE_V1',
    'base':'V88 MediaPipe extraction gate',
    'generatedAt': datetime.datetime.utcnow().isoformat()+'Z',
    'candidateFrames': len(rows_out),
    'poseSourcePassRows': sum(1 for r in rows_out if r['poseSourceStatusV89']=='POSE_SOURCE_PASS'),
    'poseReviewRows': sum(1 for r in rows_out if r['poseSourceStatusV89']=='POSE_REVIEW'),
    'poseFixRows': sum(1 for r in rows_out if r['poseSourceStatusV89']=='POSE_FIX_BEFORE_SOURCE_AUTHORITY'),
    'runtimeReviewRows': sum(1 for r in rows_out if r['runtimeReviewStatusV89']!='NO_EXTRA_RUNTIME_REVIEW'),
    'exerciseCount': len(exercise_records),
    'fullyPoseReadyExercises': [e['exerciseKey'] for e in exercise_records if e['overallPoseSourceStatusV89']=='POSE_SOURCE_READY_ALL_ROWS'],
    'partialPoseReadyExercises': [e['exerciseKey'] for e in exercise_records if e['overallPoseSourceStatusV89']=='POSE_SOURCE_PARTIAL_READY'],
    'mixedPoseExercises': [e['exerciseKey'] for e in exercise_records if e['overallPoseSourceStatusV89']=='POSE_SOURCE_MIXED'],
    'runtimeReviewExercises': [e['exerciseKey'] for e in exercise_records if e['overallRuntimeStatusV89']=='RUNTIME_REVIEW_REQUIRED'],
    'policy':'Separate pose-source authority from runtime-sequence/gait review. Far-side arm visibility is optional for selected lower-body exercises when required full-body landmarks are strong.'
}

report={'summary':summary,'exerciseRecords':exercise_records,'rows':rows_out}
with open(OUT_JSON,'w') as f: json.dump(report,f,indent=2)
with open(OUT_JS_JSON,'w') as f: json.dump(report,f,indent=2)
with open(OUT_JS,'w') as f: f.write('window.NEXT11_STAGE2_PHASE3_V89_EXERCISE_AWARE_GATE = '+json.dumps(report, indent=2)+';\n')
print('wrote', OUT_JSON)
print(json.dumps(summary, indent=2))
