import os, sys, subprocess as sp, time, json, pathlib
ROOT=pathlib.Path(__file__).resolve().parent
repo=pathlib.Path(sys.argv[1]).resolve(); side=sys.argv[2]
rewrite = '--rewrite' in sys.argv[3:]
out=ROOT/(side+'-'+time.strftime('%Y%m%d-%H%M%S')); out.mkdir()
home=out/'home'; (home/'userdata').mkdir(parents=True)
settings=home/'userdata/desktop-settings.json'
settings.write_text(json.dumps({'localEnvironmentEnabled':False,'serverExposureMode':'tailnet'}))
# Skip unrelated first-run history import and keep all provider homes synthetic.
if rewrite:
    (home/'userdata/client-settings.json').write_text(json.dumps({'onboardingCompletedAt':'2026-11-13T00:00:00.000Z'}))
userhome=out/'synthetic-user'; userhome.mkdir()
env=os.environ.copy()
for k in ['ELECTRON_RUN_AS_NODE','VITE_DEV_SERVER_URL','VITE_HTTP_URL','VITE_WS_URL','T3CODE_DESKTOP_DEV']:
    env.pop(k,None)
env.update(CLAUDE_CONFIG_DIR=str(userhome/'.claude'),CODEX_HOME=str(userhome/'.codex'),PI_CODING_AGENT_DIR=str(userhome/'.pi'),T3CODE_HOME=str(home),T3CODE_DEV_SEED='0',XDG_CONFIG_HOME=str(out/'config'),XDG_DATA_HOME=str(out/'data'),T3CODE_TELEMETRY_ENABLED='0')
procs=[]; records=[]
def launch(args,name,**kw):
    log=open(out/(name+'.log'),'w'); p=sp.Popen(args,stdout=log,stderr=sp.STDOUT,env=env,**kw); procs.append(p); return p
def stop(p):
    if p.poll() is None:
        p.terminate()
        try:p.wait(15)
        except sp.TimeoutExpired:p.kill(); p.wait()
def cmd(*args):return sp.check_output(args,stderr=sp.DEVNULL).decode().strip()
def windows():
    ids=cmd('xdotool','search','--onlyvisible','--name','T3|This T3|^Error$').splitlines()
    return [(i,cmd('xdotool','getwindowname',i)) for i in ids]
def stable_capture(name):
    last=None
    for n in range(30):
        raw=sp.check_output(['import','-window','root','-strip','png:-'])
        if last==raw:
            (out/(name+'.png')).write_bytes(raw);return
        last=raw; time.sleep(.5)
    raise RuntimeError('screen did not stabilize')
def startserver():
    p=launch(['node',str(repo/'apps/server/dist/bin.mjs'),'serve','--base-dir',str(home),'--host','127.0.0.1','--port','17373'],'server')
    for n in range(120):
        if (home/'userdata/server-runtime.json').exists():return p
        if p.poll() is not None:raise RuntimeError('server exited')
        time.sleep(.5)
    raise RuntimeError('server not ready')
def electron():
    return launch([str(repo/'apps/desktop/node_modules/electron/dist/electron'),str(repo/'apps/desktop/dist-electron/boot.cjs'),'--no-sandbox','--disable-gpu','--force-color-profile=srgb'], 'electron-'+str(len(records)),cwd=repo/'apps/desktop')
try:
    wm=launch(['openbox'],'wm'); time.sleep(1)
    server=None if rewrite else startserver(); time.sleep(2)
    ff=launch(['ffmpeg','-y','-f','x11grab','-video_size','1920x1080','-framerate','12','-i',os.environ['DISPLAY'],'-an','-c:v','libx264','-preset','ultrafast','-crf','23','-pix_fmt','yuv420p',str(out/'restart-cycles.mp4')],'recording')
    for cycle in range(1,3 if rewrite else 4):
        p=electron(); found=[]
        for n in range(150):
            try:found=windows()
            except sp.CalledProcessError:found=[]
            if found:break
            if p.poll() is not None:raise RuntimeError('electron exited before window')
            time.sleep(.5)
        if not found:raise RuntimeError('no window')
        # Give the real renderer time to load; native startup conflicts are modal.
        time.sleep(3)
        found=windows(); print('cycle',cycle,found,flush=True)
        conflict=any(('unavailable' in name or name=='Error') for _,name in found)
        if not conflict:
            time.sleep(10)
            wid=found[-1][0]
            sp.run(['xdotool','windowsize',wid,'1200','800'],check=True)
            sp.run(['xdotool','windowmove',wid,'360','140'],check=True)
            time.sleep(1)
        stable_capture('restart-'+str(cycle))
        records.append({'cycle':cycle,'conflict':conflict,'settingsBeforeQuit':json.loads(settings.read_text())})
        if conflict:
            sp.run(['xdotool','key','Return'],check=True)
        else:
            sp.run(['xdotool','windowactivate','--sync',found[-1][0]],check=True)
            sp.run(['xdotool','key','alt+F4'],check=True)
        try:p.wait(15)
        except sp.TimeoutExpired:stop(p)
        time.sleep(1)
        records[-1]['settingsAfterQuit']=json.loads(settings.read_text())
        if rewrite and cycle==1:
            server=startserver(); time.sleep(2)
        print(json.dumps(records[-1]),flush=True)
    stop(ff)
    if server is not None:stop(server)
    (out/'observations.json').write_text(json.dumps(records,indent=2))
    print('OUTPUT',out,flush=True)
finally:
    for p in reversed(procs):stop(p)
