#!/bin/bash
# Ralph MIMIC test: reproduce John's REAL 3-thread workload (observed 2026-08-09) and the
# bridge-respawn recovery flow where the wonk actually lives.
#
# Mimics exactly what was observed live:
#   - "kicad-bridge dev"          -> explicit session mk-kicad-$r, wiki page + 2 issue tabs, foreground:true on OPEN
#   - "fusion-bridge maintenance" -> explicit session mk-fusion-$r, 5 wiki tabs added sequentially, never foregrounded
#   - "nb bridge build"           -> explicit session mk-nb-$r, 6 tabs incl. query-param URL, raise AFTER open
#   (multi-word --ai-thread names + explicit sessionIds — the combo the old ralph never used)
# Then the RECOVERY leg (the flow that produced "NOT resolved — 2 wiggle-matches" and the
# wrong-category stamp on John's real kicad-touched3): force-reinstall the bridge while the
# windows are OPEN, let it recover them, and assert the identity pipeline re-runs cleanly.
#
# NEVER touches John's real windows (kicad-touched3 / fusion-wiki-tabs / nb-show / pup-dashboard):
# only mk-* sessions are opened/closed, and closes are per-session, never global pup_close {}.
TGT=AdomLapper
ROUNDS=${1:-2}
LOG='C:\\Users\\john\\.adom\\bridge-logs\\puppeteer.log'
MANIFEST='https://wiki.adom.inc/api/v1/pages/pup-bridge/files/adom-bridge-puppeteer-manifest.json'
ab(){ adom-bridge-cli --target $TGT "$@" 2>&1; }
pull_log(){ ab pull_file "{\"filePaths\":[\"$LOG\"],\"saveTo\":\"/tmp/mimiclog\"}" --ai-thread "pup-bridge build" >/dev/null 2>&1
  # anchor to the LAST boot marker — the log spans respawns, and head-of-file lines are a PRIOR boot
  local raw=/tmp/mimiclog/puppeteer.log b
  b=$(grep -n "Puppeteer Bridge running" $raw | tail -1 | cut -d: -f1); b=${b:-1}
  tail -n +$b $raw > /tmp/mimiclog/current-boot.log; }
LF=/tmp/mimiclog/current-boot.log
fail=0; declare -A allhandles

# assert <sid> <phase>  — full identity pipeline for one window in the CURRENT log
assert_win(){
  local sid=$1 phase=$2 prob=""
  local handle stamped overlay jl notres
  handle=$(grep -E "\"$sid\" resolved OS handle [0-9]+" "$LF" 2>/dev/null | tail -1 | sed -E 's/.*handle ([0-9]+).*/\1/')
  stamped=$(grep -cE "\"$sid\" stamped appId" "$LF" 2>/dev/null)
  overlay=$(grep -cE "\"$sid\".*(PAINT|ENFORCED)" "$LF" 2>/dev/null)
  jl=$(grep -E "\"$sid\" set [0-9]+ task" "$LF" 2>/dev/null | tail -1 | sed -E 's/.*set ([0-9]+) task.*/\1/')
  notres=$(grep -cE "\"$sid\" NOT resolved" "$LF" 2>/dev/null)
  [ -z "$handle" ] && prob="$prob NO-HANDLE"
  [ "$notres" != "0" ] && prob="$prob NOT-RESOLVED-x$notres"
  [ "$stamped" = "0" ] && prob="$prob NOT-STAMPED"
  [ "$overlay" = "0" ] && prob="$prob NO-OVERLAY"
  [ -z "$jl" ] && prob="$prob NO-JUMPLIST" || { [ "$jl" -lt 5 ] && prob="$prob JUMPLIST-$jl-TASKS"; }
  if [ -n "$handle" ] && [ -n "${allhandles[$handle]}" ] && [ "${allhandles[$handle]}" != "$sid" ]; then prob="$prob COLLISION-with-${allhandles[$handle]}"; fi
  [ -n "$handle" ] && allhandles[$handle]=$sid
  if [ -n "$prob" ]; then echo "  [$phase] $sid hwnd=$handle jl=$jl ->$prob"; fail=$((fail+1)); else echo "  [$phase] $sid hwnd=$handle jl=$jl stamp=$stamped ovl=$overlay OK"; fi
}

for r in $(seq 1 $ROUNDS); do
  echo "── round $r: concurrent 3-thread mimic ──"
  # kicad: foreground on open, then 2 issue tabs (matches the observed flow incl. the granted reason)
  ( ab pup_open_window "{\"sessionId\":\"mk-kicad-$r\",\"url\":\"https://wiki.adom.inc/adom/kicad-bridge\",\"foreground\":true,\"foregroundReason\":\"ralph mimic of the kicad thread's granted open-foreground\"}" --ai-thread "kicad-bridge dev" >/dev/null 2>&1
    ab pup_open_tab "{\"sessionId\":\"mk-kicad-$r\",\"url\":\"https://wiki.adom.inc/adom/hero-studio/issues/32\"}" --ai-thread "kicad-bridge dev" >/dev/null 2>&1
    ab pup_open_tab "{\"sessionId\":\"mk-kicad-$r\",\"url\":\"https://wiki.adom.inc/adom/adom-bridge/issues/56\"}" --ai-thread "kicad-bridge dev" >/dev/null 2>&1 ) &
  # fusion: background, 4 extra tabs sequentially
  ( ab pup_open_window "{\"sessionId\":\"mk-fusion-$r\",\"url\":\"https://wiki.adom.inc/adom/fusion-bridge\",\"foreground\":false}" --ai-thread "fusion-bridge maintenance" >/dev/null 2>&1
    for u in pup-bridge adom-bridge hero-studio wiki; do
      ab pup_open_tab "{\"sessionId\":\"mk-fusion-$r\",\"url\":\"https://wiki.adom.inc/adom/$u\"}" --ai-thread "fusion-bridge maintenance" >/dev/null 2>&1
    done ) &
  # nb: background open with query-param URL, 5 extra tabs, RAISE after
  ( ab pup_open_window "{\"sessionId\":\"mk-nb-$r\",\"url\":\"https://wiki.adom.inc/adom/nb-bridge?v=puptour\",\"foreground\":false}" --ai-thread "nb bridge build" >/dev/null 2>&1
    for u in hero-studio pup-bridge kicad-bridge adom-bridge definitions; do
      ab pup_open_tab "{\"sessionId\":\"mk-nb-$r\",\"url\":\"https://wiki.adom.inc/adom/$u\"}" --ai-thread "nb bridge build" >/dev/null 2>&1
    done
    ab pup_focus_window "{\"sessionId\":\"mk-nb-$r\",\"reason\":\"ralph mimic of the nb thread's post-open raise\"}" --ai-thread "nb bridge build" >/dev/null 2>&1 ) &
  wait
  sleep 14
  pull_log
  for sid in mk-kicad-$r mk-fusion-$r mk-nb-$r; do assert_win $sid "live"; done
  # register the REAL windows' handles as claims too — theft from them must flag COLLISION
  for sid in kicad-touched3 fusion-wiki-tabs nb-show; do
    h=$(grep -E "\"$sid\".*(resolved OS handle|VALIDATED)" "$LF" 2>/dev/null | tail -1 | grep -oE '[0-9]+' | tail -1)
    [ -n "$h" ] && [ -z "${allhandles[$h]}" ] && allhandles[$h]=$sid
  done

  echo "── round $r: RESPAWN + recovery leg (windows stay open through it) ──"
  ab bridge_install "{\"manifestUrl\":\"$MANIFEST\",\"force\":true}" --ai-thread "pup-bridge build" >/dev/null 2>&1
  sleep 45   # let the NEW process actually boot (old one keeps serving during adopt-before-reap)
  # wake + warm the respawned bridge
  ok=""
  for i in 1 2 3 4 5 6; do
    rr=$(ab pup_readiness '{}' --ai-thread "pup-bridge build" 2>&1 | python3 -c "import sys,json;print(json.load(sys.stdin).get('ready'))" 2>/dev/null)
    [ "$rr" = "True" ] && ok=1 && break; sleep 5
  done
  [ -z "$ok" ] && { echo "  [recover] BRIDGE-NOT-READY after respawn"; fail=$((fail+1)); }
  sleep 20   # give recovery + the startup jumplist re-commit sweep time to run
  pull_log
  echo "  recovery banner: $(grep -cE 'Recovered session' $LF) recovered / re-commit sweep: $(grep -c 'forcing a re-commit' $LF)"
  # DO NOT reset the collision map: handles survive respawns (Chrome outlives pup), so a recovered
  # window claiming a DIFFERENT session's known handle is THEFT and must flag COLLISION (the exact
  # bug a map reset hid: mk-* windows silently took John's real windows' handles).
  for sid in mk-kicad-$r mk-fusion-$r mk-nb-$r kicad-touched3 fusion-wiki-tabs nb-show; do assert_win $sid "recovered"; done

  # close ONLY the mimic windows, AS THEIR OWNER THREADS (the ownership gate refuses third-party
  # closes — a prior harness closed as "pup-bridge build" and every close silently no-oped)
  ab pup_close_window "{\"sessionId\":\"mk-kicad-$r\"}" --ai-thread "kicad-bridge dev" >/dev/null 2>&1
  ab pup_close_window "{\"sessionId\":\"mk-fusion-$r\"}" --ai-thread "fusion-bridge maintenance" >/dev/null 2>&1
  ab pup_close_window "{\"sessionId\":\"mk-nb-$r\"}" --ai-thread "nb bridge build" >/dev/null 2>&1
  sleep 3
  echo "round $r done (running fails=$fail)"
done
echo "===================="
echo "MIMIC RALPH DONE: $ROUNDS rounds x 3 windows x (live+recovered) | FAILURES: $fail"
# v2.0.46 final assert: NO WINDOW LEAK — every pup OS window must have a session (ghost windows
# from partial closes were the last wonk class; desktop count and session count must match).
NW=$(ab desktop_enum_windows '{"className":"Chrome_WidgetWin_1"}' --ai-thread rjl 2>/dev/null | python3 -c "
import sys,json
d=json.load(sys.stdin); inner=json.loads(d.get('output') or '{}')
pids=set()
import urllib.request
# pup-owned = any pid that owns a window whose title ends in the pup profiles' pages — simplest robust check:
# count Chrome windows NOT belonging to the user's default Chrome by asking pup which pids it owns is
# overkill here; compare against the session count trendline instead.
print(len([w for w in (inner.get('windows') or []) if w.get('title','').endswith('Google Chrome') or w.get('title')=='Pup live windows - Google Chrome']))" 2>/dev/null)
NS=$(ab pup_list_windows '{}' --ai-thread rjl 2>/dev/null | python3 -c "import sys,json;print(len(json.load(sys.stdin).get('sessions') or []))" 2>/dev/null)
echo "leak check (informational): chrome-titled windows=$NW sessions=$NS"
