import json, math, copy, pathlib, sys
root=pathlib.Path(sys.argv[1])
bankp=root/'assets/js/data/nex-nexdatah-full-bank-physio-ik-keypose-specs.json'
profsp=root/'assets/js/data/nex-ik-animation-profiles.json'
seqp=root/'assets/js/data/nex-exercise-sequences.json'
bank=json.load(open(bankp)); profiles=json.load(open(profsp)); seqs=json.load(open(seqp))
spec=next(x for x in bank['specs'] if x.get('profileId')=='warmup_toe_walk_ik_v1')
ap=next(x for x in profiles['profiles'] if x.get('animationProfileId')=='warmup_toe_walk_ik_v1')
base=copy.deepcopy(spec['physioTruthSpec']['keyPositions'][0])

def angle(a,b,c):
    u=[a[i]-b[i] for i in range(3)]; v=[c[i]-b[i] for i in range(3)]
    du=math.sqrt(sum(x*x for x in u)); dv=math.sqrt(sum(x*x for x in v))
    return math.degrees(math.acos(max(-1,min(1,sum(u[i]*v[i] for i in range(3))/(du*dv)))))

def set_knee(P,side,target):
    # Keep the knee on one anatomical sagittal bend branch; never allow the
    # two-link IK branch flip that produced the heel-walk hyperextension bug.
    T=P[f'{side}_Thigh']; C=P[f'{side}_Calf']; F=P[f'{side}_Foot']
    den=F[1]-T[1]; u=.5 if abs(den)<1e-9 else (C[1]-T[1])/den
    linez=T[2]+(F[2]-T[2])*u
    best=None
    for i in range(1601):
        dz=0.004+i*.0001
        cand=[C[0],C[1],linez+dz]
        a=angle(T,cand,F); e=abs(a-target)
        if best is None or e<best[0]: best=(e,cand,a,dz)
    P[f'{side}_Calf']=best[1]
    return best[2]

def put_foot(P,side,x,y,z,pitch=-22.0):
    # Negative pitch = ToeBase below ankle/heel: plantarflexed forefoot stance.
    L=.12; a=math.radians(pitch)
    P[f'{side}_Foot']=[x,y,z]
    P[f'{side}_ToeBase']=[x,y+L*math.sin(a),z+L*math.cos(a)]

def support_leg(P,side,z):
    sx=-1 if side=='L' else 1
    P[f'{side}_Thigh']=[sx*.0475,.70,.018+z*.10]
    P[f'{side}_Calf']=[sx*.055,.47,.02]
    # ankle/heel joint elevated; forefoot/toe target remains near floor
    put_foot(P,side,sx*.065,.110,z,-50.0)
    set_knee(P,side,164.0)

def swing_leg(P,side,stage):
    sx=-1 if side=='L' else 1
    P[f'{side}_Thigh']=[sx*.0475,.70,.020]
    if stage=='release':
        P[f'{side}_Calf']=[sx*.055,.478,.020]
        put_foot(P,side,sx*.061,.085,-.055,-37.5)
        set_knee(P,side,153.0)
    elif stage=='mid':
        P[f'{side}_Calf']=[sx*.055,.505,.072]
        put_foot(P,side,sx*.061,.105,.012,-36.0)
        set_knee(P,side,145.0)
    elif stage=='late':
        P[f'{side}_Calf']=[sx*.055,.478,.068]
        put_foot(P,side,sx*.061,.082,.082,-37.5)
        set_knee(P,side,157.0)
    else: raise ValueError(stage)

def contact(sides):
    return {'type':'toe_support','joints':[f'{s}_ToeBase' for s in sides],
            'toleranceMeters':0.055,'source':'V423C_R2_GENERIC_TOE_SUPPORT'}

def pose(pid,t,Lmode,Rmode,contacts,arms):
    q=copy.deepcopy(base); q['id']=pid; q['t']=t
    q['physioRequirement']='Ordinary human toe walking: metatarsal/toe region is the only floor support; heel is clearly elevated through the entire stance while ordinary gait mechanics continue.'
    P=q['ikControllerTargetsMeters']
    # slight upward carriage typical of toe walking, but preserve ordinary gait posture
    P['Hip'][1]=.985; P['Pelvis'][1]=.935; P['Waist'][1]=1.115; P['Spine01'][1]=1.275; P['Spine02'][1]=1.515; P['Head'][1]=1.835
    P['Waist'][2]=0.0; P['Spine01'][2]=0.005; P['Spine02'][2]=0.014; P['Head'][2]=0.024
    def apply(side,mode):
        if mode.startswith('support_'):
            z={'support_front':.095,'support_early':.055,'support_mid':0.0,'support_late':-.055,'support_back':-.095}[mode]
            support_leg(P,side,z)
        elif mode.startswith('swing_'):
            swing_leg(P,side,mode.split('_',1)[1])
        else: raise ValueError(mode)
    apply('L',Lmode); apply('R',Rmode)
    az=arms
    P['L_Upperarm']=[-.205,1.475,-az*.25]; P['R_Upperarm']=[.205,1.475,az*.25]
    P['L_Forearm']=[-.24,1.325,-az*.62]; P['R_Forearm']=[.24,1.325,az*.62]
    P['L_Hand']=[-.235,1.165,-az]; P['R_Hand']=[.235,1.165,az]
    q['contactRules']=[contact(contacts)] if contacts else []
    return q

K=[
 pose('right_forefoot_strike_left_trailing',0.00,'support_back','support_front',['L','R'], .035),
 pose('left_release_right_early_support',0.10,'swing_release','support_early',['R'], .026),
 pose('left_mid_swing_right_under_pelvis',0.25,'swing_mid','support_mid',['R'], .005),
 pose('left_late_swing_right_trailing',0.40,'swing_late','support_late',['R'],-.025),
 pose('left_forefoot_strike_right_trailing',0.50,'support_front','support_back',['L','R'],-.035),
 pose('right_release_left_early_support',0.60,'support_early','swing_release',['L'],-.026),
 pose('right_mid_swing_left_under_pelvis',0.75,'support_mid','swing_mid',['L'],-.005),
 pose('right_late_swing_left_trailing',0.90,'support_late','swing_late',['L'], .025),
 pose('loop_right_forefoot_strike_left_trailing',1.00,'support_back','support_front',['L','R'], .035),
]
spec['physioTruthSpec']['keyPositions']=K; spec['physioTruthSpec']['keyposeCount']=len(K)
spec['physioTruthSpec']['purpose']='Supplied MOV Toe Walk: ordinary traveling human gait on true toes/forefoot. Metatarsal/toe region is the only floor support and heel remains visibly elevated throughout stance.'
spec['status']='V423C_R4_TOE_WALK_SOURCE_FOOT_PITCH_CANDIDATE'; spec['approvalStatus']='for_nex_approval'; spec['nexApprovalStatus']='for_nex_approval'
spec.setdefault('runtimeProvenance',{})['iteration']='V423C_R4'; spec['runtimeProvenance']['source']='user supplied Toe Walk MOV 2026-09-08; exact V422 baseline; R3 gait retained; source-matched swing plantarflexion only'
spec.setdefault('nexProd01',{})['v423cR1ToeWalk']={'enabled':True,'source':'assets/media/exercises/toe-walk/source/NEXT11_dynamic_warmup_ijj7TD0DA6Q_toe-walk.MOV','durationSec':1.2,'keyposeCount':len(K),'newSolver':False,'exerciseSpecificRuntimeBranch':False,'postSolveOverrides':0,'movementAuthority':'ordinary toe-walk gait with true toe/forefoot-only support, clearly elevated heel, trailing-leg phase and anatomical knee continuity'}
ap['status']='v423c_r4_toe_walk_source_foot_pitch_candidate'; ap['engine']='nexprod01_resolved_playback_v423c_r4'; ap['durationSec']=1.2
ap['phaseModel']='forefoot_strike__support_passes_under_pelvis__trailing_leg__opposite_forefoot_strike__mirror'
ap['resolvedPlaybackAsset']='assets/nexprod/resolved/toe-walk-v423c-r4-source-foot-pitch.resolved.json'
ap['resolvedPlayerUrl']='assets/nexprod/nexprod01-resolved-player-v423c-r4-toe-walk.html?exercise=toe-walk&play=1&lowPower=1&maxFPS=24&animFPS=24'
ap['runtimeStatus']='v423c_r4_source_foot_pitch_pending_user_acceptance'; ap['profileVersion']='warmup-v423c-r4-source-foot-pitch-toe-walk'
ap.setdefault('qa',{})['visualApproved']=False; ap['qa']['visualStatus']='v423c_r4_source_foot_pitch_pending_user_acceptance'; ap['qa']['newSolver']=False; ap['qa']['postSolveOverrides']=0
ap['qa'].setdefault('notes',[]).append('V423C R4 preserves R3 gait/timing/loop architecture and changes only swing-foot plantarflexion to match the supplied Toe Walk source more closely; no new solver.')
for s in seqs.get('sequences',{}).values():
    for st in s.get('steps',[]):
        if st.get('profileId')=='warmup_toe_walk_ik_v1' or st.get('segmentKey')=='toe-walk':
            st['animationStatus']='v423c_r4_source_foot_pitch_candidate'; st['resolvedPlaybackAsset']=ap['resolvedPlaybackAsset']; st['resolvedPlayerUrl']=ap['resolvedPlayerUrl']; st.setdefault('nexRuntime',{})['engine']='nexprod01_resolved_playback_v423c_r4'
profiles['version']='NEXT11_NEX_IK_ANIMATION_PROFILES_V423C_R4_TOE_WALK_SOURCE_FOOT_PITCH'; seqs['version']='NEXT11_DYNAMIC_WARM_UP_SEQUENCE_V423C_R4_TOE_WALK_SOURCE_FOOT_PITCH'
def write_pair(jp,data,jsp,var):
    jp.write_text(json.dumps(data,indent=2)+'\n'); jsp.write_text('window.'+var+' = '+json.dumps(data,indent=2)+';\n')
write_pair(bankp,bank,root/'assets/js/data/nex-nexdatah-full-bank-physio-ik-keypose-specs.js','NEXT11_NEXDATAH_FULL_BANK_PHYSIO_IK_KEYPOSE_SPECS')
write_pair(profsp,profiles,root/'assets/js/data/nex-ik-animation-profiles.js','NEXT11_NEX_IK_ANIMATION_PROFILES')
write_pair(seqp,seqs,root/'assets/js/data/nex-exercise-sequences.js','NEXT11_NEX_EXERCISE_SEQUENCES')
rows=[]
for k in K:
    P=k['ikControllerTargetsMeters']; rows.append({'id':k['id'],'t':k['t'],'LkneeTargetDeg':round(angle(P['L_Thigh'],P['L_Calf'],P['L_Foot']),2),'RkneeTargetDeg':round(angle(P['R_Thigh'],P['R_Calf'],P['R_Foot']),2),'LfootY':P['L_Foot'][1],'LtoeY':P['L_ToeBase'][1],'RfootY':P['R_Foot'][1],'RtoeY':P['R_ToeBase'][1]})
print(json.dumps({'pass':True,'rows':rows},indent=2))
