#!/usr/bin/env bash
# Tests for claudeCode — Claude Code session management
# Tests: process.find, session.save, session.recover, session.name, context.check, list.named

level=$1
if [ -z "$level" ]; then
  level=1
fi
echo "starting: ${BASH_SOURCE[@]##*/} <LOG_LEVEL=$level>"

source this
source test.suite

log.level $level

# ============================================================================
# T1: claudeCode is callable
# ============================================================================
test.case $level "claudeCode is callable" \
  which claudeCode

if which claudeCode &>/dev/null; then
  expect.pass "claudeCode found on PATH"
else
  expect.fail "claudeCode should be on PATH"
fi

# ============================================================================
# T2: claudeCode.process.find function exists
# ============================================================================
test.case $level "claudeCode.process.find function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.process.find'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.process.find 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.process.find function exists"
else
  expect.fail "claudeCode.process.find should be a function"
fi

# ============================================================================
# T3: claudeCode.session.save function exists
# ============================================================================
test.case $level "claudeCode.session.save function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.session.save'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.session.save 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.session.save function exists"
else
  expect.fail "claudeCode.session.save should be a function"
fi

# ============================================================================
# T4: claudeCode.session.recover function exists
# ============================================================================
test.case $level "claudeCode.session.recover function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.session.recover'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.session.recover 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.session.recover function exists"
else
  expect.fail "claudeCode.session.recover should be a function"
fi

# ============================================================================
# T5: claudeCode.session.name function exists
# ============================================================================
test.case $level "claudeCode.session.name function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.session.name'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.session.name 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.session.name function exists"
else
  expect.fail "claudeCode.session.name should be a function"
fi

# ============================================================================
# T6: claudeCode.context.check function exists
# ============================================================================
test.case $level "claudeCode.context.check function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.context.check'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.context.check 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.context.check function exists"
else
  expect.fail "claudeCode.context.check should be a function"
fi

# ============================================================================
# T7: claudeCode.list.named function exists
# ============================================================================
test.case $level "claudeCode.list.named function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.list.named'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.list.named 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.list.named function exists"
else
  expect.fail "claudeCode.list.named should be a function"
fi

# ============================================================================
# T8: claudeCode.session.id function exists
# ============================================================================
test.case $level "claudeCode.session.id function exists" \
  bash -c 'source this; source claudeCode; type -t claudeCode.session.id'

FUNC_EXISTS=$(bash -c 'source this; source claudeCode; type -t claudeCode.session.id 2>/dev/null')
if [ "$FUNC_EXISTS" = "function" ]; then
  expect.pass "claudeCode.session.id function exists"
else
  expect.fail "claudeCode.session.id should be a function"
fi

# ============================================================================
# T9: claudeCode.context.jsonl function accepts pane param
# ============================================================================
test.case $level "claudeCode.context.jsonl accepts pane parameter" \
  bash -c 'source this; source claudeCode; declare -f claudeCode.context.jsonl | grep -q "pane"'

HAS_PANE=$(bash -c 'source this; source claudeCode; declare -f claudeCode.context.jsonl 2>/dev/null | grep -c "pane"')
if [ "$HAS_PANE" -ge 1 ]; then
  expect.pass "claudeCode.context.jsonl references pane parameter"
else
  expect.fail "claudeCode.context.jsonl should accept pane parameter for per-agent JSONL resolution"
fi

# ============================================================================
# T10: claudeCode.agent.start does NOT use --dangerously-skip-permissions
# ============================================================================
# Check agent.start specifically — claudeCode.dangerously() is a separate opt-in function
test.case $level "claudeCode.agent.start does NOT use --dangerously-skip-permissions" \
  echo "checking agent.start"

# Extract agent.start function body only (between function def and next function)
SKIP_IN_AGENT_START=$(sed -n '/^claudeCode\.agent\.start()/,/^claudeCode\./p' "$OOSH_DIR/claudeCode" | grep -c "dangerously-skip-permissions")
if [ "$SKIP_IN_AGENT_START" -eq 0 ]; then
  expect.pass "claudeCode.agent.start does NOT use --dangerously-skip-permissions (security OK)"
else
  expect.fail "CRITICAL: claudeCode.agent.start uses --dangerously-skip-permissions — PO violation"
fi

# ============================================================================
# LIVE BEHAVIORAL TESTS — session.id accuracy vs /status ground truth
# DISABLED: These tests send /status to every Claude pane, which:
#   1. Disrupts running agents (sends commands to their active sessions)
#   2. Only works on this specific machine with the current tmux layout
#   3. Takes ~4s per pane — very slow for large session counts
# TODO: Redesign as fixture-based test using ooshDebug with known panes.
# Use T-ALIGN-1 (ps --resume UUID comparison) instead — non-disruptive.

LIVE_PANES=""  # Still needed by T-IDLE and T-ALIGN tests below
CURRENT_SESSION=""
while IFS= read -r line; do
  if echo "$line" | grep -qE '^[├└]── '; then
    CURRENT_SESSION=$(echo "$line" | sed 's/^[├└]── //' | sed 's/ (.*//')
    continue
  fi
  if echo "$line" | grep -qE '\[[0-9]+\.[0-9]+\.[0-9]+\]'; then
    PANE_ADDR=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+' | head -1)
    if [ -n "$CURRENT_SESSION" ] && [ -n "$PANE_ADDR" ]; then
      LIVE_PANES="$LIVE_PANES ${CURRENT_SESSION}:${PANE_ADDR}"
    fi
  fi
done < <(otmux 2>/dev/null)

LIVE_TESTED=0
LIVE_PASSED=0
LIVE_FAILED=0

test.case $level "session.id /status live test (DISABLED — disruptive to agents)" \
  echo "skipped"
expect.pass "session.id /status live test skipped (use T-ALIGN-1 for non-disruptive UUID validation)"

# ============================================================================
# T-IDLE: session.id returns exit 1 for idle (non-Claude) pane
# ============================================================================

# Find a pane NOT in LIVE_PANES that runs a plain shell
IDLE_PANE=""
while IFS= read -r line; do
  # Pane lines with [bash] or [zsh] — no Claude version
  if echo "$line" | grep -qE '\[(bash|zsh)\]'; then
    PANE_ADDR=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+' | head -1)
    if [ -n "$CURRENT_SESSION" ] && [ -n "$PANE_ADDR" ]; then
      candidate="${CURRENT_SESSION}:${PANE_ADDR}"
      # Verify no claude PID behind it
      if ! claudeCode process.find "$candidate" &>/dev/null 2>&1; then
        IDLE_PANE="$candidate"
        break
      fi
    fi
  fi
  # Track current session for context
  if echo "$line" | grep -qE '^[├└]── '; then
    CURRENT_SESSION=$(echo "$line" | sed 's/^[├└]── //' | sed 's/ (.*//')
  fi
done < <(otmux 2>/dev/null)

if [ -n "$IDLE_PANE" ]; then
  test.case $level "session.id returns exit 1 for idle pane $IDLE_PANE" \
    claudeCode session.id "$IDLE_PANE"

  claudeCode session.id "$IDLE_PANE" >/dev/null 2>&1
  IDLE_EXIT=$?
  IDLE_OUTPUT=$(claudeCode session.id "$IDLE_PANE" 2>/dev/null || true)

  if [ "$IDLE_EXIT" -ne 0 ] && [ -z "$IDLE_OUTPUT" ]; then
    expect.pass "session.id correctly returns exit 1 + empty for idle pane $IDLE_PANE"
  else
    expect.fail "session.id should return exit 1 + empty for idle pane, got exit=$IDLE_EXIT output='$IDLE_OUTPUT'"
  fi
else
  test.case $level "session.id idle pane test (skipped - no idle pane found)" \
    echo "skipped"
  expect.pass "session.id idle pane test skipped"
fi

# ============================================================================
# T-REGISTRY: hiveMind registry matches live session identity for ALL panes
# ============================================================================

REG_FILE="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
if [ -f "$REG_FILE" ]; then
  for pane in $LIVE_PANES; do
    # Get registry role for this pane
    REG_ROLE=$(grep "^${pane}|" "$REG_FILE" 2>/dev/null | cut -d'|' -f2)
    [ -z "$REG_ROLE" ] && continue

    # Get actual session name
    SID=$(claudeCode session.id "$pane" 2>/dev/null || true)
    [ -z "$SID" ] && continue
    SNAME=$(claudeCode session.name "$SID" 2>/dev/null || true)
    [ -z "$SNAME" ] && continue

    # Extract role from session name (role@model format)
    if [[ "$SNAME" == *"@"* ]]; then
      ACTUAL_ROLE="${SNAME%%@*}"
    else
      # Not a /rename'd session — skip registry check (can't compare)
      continue
    fi

    test.case $level "registry matches session.name for $pane" \
      echo "registry='$REG_ROLE' session='$ACTUAL_ROLE'"

    if [ "$REG_ROLE" = "$ACTUAL_ROLE" ]; then
      expect.pass "registry $pane = $REG_ROLE (matches session.name)"
    else
      expect.fail "registry MISMATCH $pane: registry='$REG_ROLE' but session='$ACTUAL_ROLE'"
    fi
  done
fi

# ============================================================================
# IDENTITY ALIGNMENT TESTS — 4-layer chain integrity
# Layer 1: Pane → Registry Role (hivemind.roles.env)
# Layer 2: Role → Session UUID (hivemind.sessions.env)
# Layer 3: session.id output → actual process UUID (ps --resume args)
# Layer 4: Registry role name validity (not boot prompt garbage)
#
# These tests discover when the identity chain is broken. They don't
# need /status (slow, intrusive) — they compare what the system CLAIMS
# against what ps and the file system actually show.
# ============================================================================

REG_FILE="${HIVEMIND_REGISTRY:-${CONFIG_PATH:-$HOME/config}/hivemind.roles.env}"
SES_FILE="${HIVEMIND_SESSIONS:-${CONFIG_PATH:-$HOME/config}/hivemind.sessions.env}"

ALIGN_TESTED=0
ALIGN_PASSED=0
ALIGN_FAILED=0

# ── T-ALIGN-1: session.id matches --resume UUID from ps ──────────────────
# For every Claude pane that was started with --resume, the UUID in ps args
# is ground truth. session.id MUST return the same value.

for pane in $LIVE_PANES; do
  claude_pid=$(claudeCode process.find "$pane" 2>/dev/null || true)
  [ -z "$claude_pid" ] && continue

  # Get the --resume UUID from ps args (ground truth when available)
  resume_uuid=$(ps -p "$claude_pid" -o args= 2>/dev/null | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' || true)
  [ -z "$resume_uuid" ] && continue  # skip panes without --resume

  sid_result=$(claudeCode session.id "$pane" 2>/dev/null || true)

  test.case $level "session.id matches --resume UUID for $pane" \
    echo "sid=$sid_result resume=$resume_uuid"

  ALIGN_TESTED=$((ALIGN_TESTED + 1))
  if [ "$sid_result" = "$resume_uuid" ]; then
    expect.pass "session.id $pane = ${sid_result:0:8}... matches --resume"
    ALIGN_PASSED=$((ALIGN_PASSED + 1))
  else
    expect.fail "session.id STALE $pane: returns ${sid_result:0:8}... but --resume is ${resume_uuid:0:8}..."
    ALIGN_FAILED=$((ALIGN_FAILED + 1))
  fi
done

# ── T-ALIGN-2: Registry role names are valid (not boot prompt garbage) ────
# A valid role name matches an agent directory in .claude/agents/.
# Boot prompt text leaking into the registry is a known bug.

AGENTS_BASE="${CLAUDE_PROJECT_DIR:-/Users/Shared/Workspaces/AI/Claude}/.claude/agents"

if [ -f "$REG_FILE" ]; then
  while IFS='|' read -r pane_target role; do
    [ -z "$role" ] && continue

    test.case $level "registry role is valid agent name: $pane_target" \
      echo "role='$role'"

    ALIGN_TESTED=$((ALIGN_TESTED + 1))

    # Check 1: role name should be short (agent names are < 30 chars)
    if [ "${#role}" -gt 30 ]; then
      expect.fail "registry GARBAGE $pane_target: role is ${#role} chars — likely boot prompt text: '${role:0:40}...'"
      ALIGN_FAILED=$((ALIGN_FAILED + 1))
      continue
    fi

    # Check 2: role name should not contain spaces (agent names use hyphens)
    if echo "$role" | grep -q ' '; then
      expect.fail "registry GARBAGE $pane_target: role contains spaces: '$role'"
      ALIGN_FAILED=$((ALIGN_FAILED + 1))
      continue
    fi

    # Check 3: role directory should exist
    if [ -d "$AGENTS_BASE/$role" ]; then
      expect.pass "registry $pane_target = '$role' (agent dir exists)"
      ALIGN_PASSED=$((ALIGN_PASSED + 1))
    else
      expect.fail "registry ORPHAN $pane_target: role='$role' has no .claude/agents/$role/ directory"
      ALIGN_FAILED=$((ALIGN_FAILED + 1))
    fi
  done < "$REG_FILE"
fi

# ── T-ALIGN-3: No duplicate role→UUID in sessions file ───────────────────
# Each role should map to exactly one UUID. Duplicates mean stale entries.

if [ -f "$SES_FILE" ]; then
  DUP_ROLES=$(cut -d'|' -f1 "$SES_FILE" | sort | uniq -d)
  test.case $level "sessions file has no duplicate role entries" \
    echo "checking $SES_FILE"

  ALIGN_TESTED=$((ALIGN_TESTED + 1))
  if [ -z "$DUP_ROLES" ]; then
    expect.pass "no duplicate role entries in sessions file"
    ALIGN_PASSED=$((ALIGN_PASSED + 1))
  else
    expect.fail "duplicate roles in sessions file: $DUP_ROLES"
    ALIGN_FAILED=$((ALIGN_FAILED + 1))
  fi
fi

# ── T-ALIGN-4: Sessions file UUIDs exist in sessions-index.json ──────────
# Every UUID in the sessions file should be a real Claude Code session.

CLAUDE_PROJECTS_DIR="$HOME/.claude/projects"
if [ -f "$SES_FILE" ]; then
  while IFS='|' read -r role sid; do
    [ -z "$sid" ] && continue

    test.case $level "sessions UUID exists in Claude index: $role" \
      echo "sid=$sid"

    ALIGN_TESTED=$((ALIGN_TESTED + 1))

    # Search all sessions-index.json files
    found=0
    for index_file in "$CLAUDE_PROJECTS_DIR"/*/sessions-index.json; do
      [ -f "$index_file" ] || continue
      if jq -e --arg sid "$sid" '.entries[] | select(.sessionId == $sid)' "$index_file" >/dev/null 2>&1; then
        found=1
        break
      fi
    done

    if [ "$found" -eq 1 ]; then
      expect.pass "sessions file $role → ${sid:0:8}... found in Claude index"
      ALIGN_PASSED=$((ALIGN_PASSED + 1))
    else
      expect.fail "sessions file $role → ${sid:0:8}... NOT in any sessions-index.json (phantom UUID)"
      ALIGN_FAILED=$((ALIGN_FAILED + 1))
    fi
  done < "$SES_FILE"
fi

# ── T-ALIGN-5: Registry pane targets still exist in tmux ─────────────────
# Stale entries for dead sessions/panes should not persist.

if [ -f "$REG_FILE" ]; then
  while IFS='|' read -r pane_target role; do
    [ -z "$pane_target" ] && continue

    test.case $level "registry pane still exists: $pane_target" \
      echo "checking tmux pane"

    ALIGN_TESTED=$((ALIGN_TESTED + 1))

    if tmux display-message -t "$pane_target" -p "#{pane_id}" >/dev/null 2>&1; then
      expect.pass "registry $pane_target ($role) — pane exists"
      ALIGN_PASSED=$((ALIGN_PASSED + 1))
    else
      expect.fail "registry STALE $pane_target ($role) — pane no longer exists in tmux"
      ALIGN_FAILED=$((ALIGN_FAILED + 1))
    fi
  done < "$REG_FILE"
fi

# ── T-ALIGN-6: claudeCode.join writes UUID back to sessions file ─────────
# When a session is resumed via claudeCode join, the new UUID should be
# reflected in the sessions file. Check panes started with 'claudeCode join'.

for pane in $LIVE_PANES; do
  claude_pid=$(claudeCode process.find "$pane" 2>/dev/null || true)
  [ -z "$claude_pid" ] && continue

  # Check if this was started via 'claudeCode join <uuid>'
  full_cmd=$(ps -p "$claude_pid" -o args= 2>/dev/null)
  echo "$full_cmd" | grep -q 'claudeCode join' || continue

  resume_uuid=$(echo "$full_cmd" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
  [ -z "$resume_uuid" ] && continue

  # Get registry role for this pane
  reg_role=$(grep "^${pane}|" "$REG_FILE" 2>/dev/null | cut -d'|' -f2)
  [ -z "$reg_role" ] && continue

  # Check if sessions file has THIS uuid for this role
  ses_uuid=$(grep "^${reg_role}|" "$SES_FILE" 2>/dev/null | tail -1 | cut -d'|' -f2)

  test.case $level "join UUID in sessions file for $pane ($reg_role)" \
    echo "resume=$resume_uuid sessions=$ses_uuid"

  ALIGN_TESTED=$((ALIGN_TESTED + 1))
  if [ "$ses_uuid" = "$resume_uuid" ]; then
    expect.pass "sessions file $reg_role → ${resume_uuid:0:8}... matches join UUID"
    ALIGN_PASSED=$((ALIGN_PASSED + 1))
  else
    expect.fail "sessions file STALE $reg_role: has ${ses_uuid:0:8}... but claudeCode join used ${resume_uuid:0:8}..."
    ALIGN_FAILED=$((ALIGN_FAILED + 1))
  fi
done

# ── T-ALIGN-7: session.id Method 0 vs Method 1 consistency ───────────────
# Method 0 (registry lookup) and Method 1 (ps args) should agree.
# If they disagree, Method 0 is returning stale data.

for pane in $LIVE_PANES; do
  claude_pid=$(claudeCode process.find "$pane" 2>/dev/null || true)
  [ -z "$claude_pid" ] && continue

  # Method 1: ps args UUID
  ps_uuid=$(ps -p "$claude_pid" -o args= 2>/dev/null | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
  [ -z "$ps_uuid" ] && continue  # can only compare if --resume was used

  # Method 0: registry → sessions file
  reg_role=$(grep "^${pane}|" "$REG_FILE" 2>/dev/null | cut -d'|' -f2)
  [ -z "$reg_role" ] && continue
  m0_uuid=$(grep "^${reg_role}|" "$SES_FILE" 2>/dev/null | tail -1 | cut -d'|' -f2)
  [ -z "$m0_uuid" ] && continue

  test.case $level "Method 0 vs Method 1 agree for $pane" \
    echo "M0=${m0_uuid:0:8} M1=${ps_uuid:0:8}"

  ALIGN_TESTED=$((ALIGN_TESTED + 1))
  if [ "$m0_uuid" = "$ps_uuid" ]; then
    expect.pass "Methods agree for $pane: ${ps_uuid:0:8}..."
    ALIGN_PASSED=$((ALIGN_PASSED + 1))
  else
    expect.fail "Method CONFLICT $pane: M0(registry)=${m0_uuid:0:8}... M1(ps)=${ps_uuid:0:8}... — M0 is stale"
    ALIGN_FAILED=$((ALIGN_FAILED + 1))
  fi
done

echo ""
echo "  identity alignment summary: $ALIGN_TESTED tested, $ALIGN_PASSED passed, $ALIGN_FAILED failed"
echo ""

# ── T-ALIGN-8: No duplicate UUIDs across different panes ────────────────
# Two different panes should NEVER share the same session.id.
# Bug 6: projectTeam:1.2, 1.3, 1.4 all returned 5fff44f4 — stale mapping.
# Shows tmux session start date per pane so you can distinguish:
#   - Same agent --resumed in a newer session (benign if old pane is dead)
#   - Stale session.id returning cached UUID for a different/dead pane (bug)

echo ""
echo "=== T-ALIGN-8: Duplicate UUID detection across panes ==="
echo ""

DUP_TESTED=0
DUP_PASSED=0
DUP_FAILED=0

# Pre-collect session creation dates
declare -A SESS_DATES
for sess_info in $(tmux list-sessions -F "#{session_name}|#{session_created}" 2>/dev/null); do
  s_name="${sess_info%%|*}"
  s_epoch="${sess_info##*|}"
  s_date=$(date -r "$s_epoch" "+%b %-d" 2>/dev/null || date -d "@$s_epoch" "+%b %-d" 2>/dev/null || echo "?")
  SESS_DATES[$s_name]="$s_date"
done

# Collect all pane→UUID mappings with metadata
declare -A UUID_MAP      # uuid → first pane that had it
declare -A UUID_HAS_PID  # uuid → 1 if first pane has a live Claude PID
DUP_REPORT=""
DUP_COUNT=0

for sess in $(tmux list-sessions -F "#{session_name}" 2>/dev/null); do
  sess_date="${SESS_DATES[$sess]:-?}"
  for pane in $(tmux list-panes -t "$sess" -s -F "#{session_name}:#{window_index}.#{pane_index}" 2>/dev/null); do
    sid=$(claudeCode session.id "$pane" 2>/dev/null || true)
    [ -z "$sid" ] && continue

    short="${sid:0:8}"
    # Check if this pane has a live Claude process
    has_pid=0
    claude_pid=$(claudeCode process.find "$pane" 2>/dev/null || true)
    [ -n "$claude_pid" ] && has_pid=1

    if [ -n "${UUID_MAP[$sid]+x}" ]; then
      # Duplicate found — get the first pane's info
      first_pane="${UUID_MAP[$sid]}"
      first_sess="${first_pane%%:*}"
      first_date="${SESS_DATES[$first_sess]:-?}"
      first_has_pid="${UUID_HAS_PID[$sid]:-0}"

      # Determine severity
      severity="STALE"
      if [ "$has_pid" -eq 1 ] && [ "$first_has_pid" -eq 1 ]; then
        severity="CONFLICT"  # both panes have live Claude — real problem
      elif [ "$has_pid" -eq 1 ] || [ "$first_has_pid" -eq 1 ]; then
        severity="STALE"     # one is live, one is dead — stale mapping
      else
        severity="GHOST"     # neither has Claude — both are stale
      fi

      pid_label_1=""
      pid_label_2=""
      [ "$first_has_pid" -eq 1 ] && pid_label_1=" (LIVE)"
      [ "$has_pid" -eq 1 ] && pid_label_2=" (LIVE)"

      DUP_REPORT="${DUP_REPORT}  ${severity}: ${short} shared by:\n"
      DUP_REPORT="${DUP_REPORT}    ${first_pane} (${first_date})${pid_label_1}\n"
      DUP_REPORT="${DUP_REPORT}    ${pane} (${sess_date})${pid_label_2}\n"
      DUP_COUNT=$((DUP_COUNT + 1))
    else
      UUID_MAP[$sid]="$pane"
      UUID_HAS_PID[$sid]="$has_pid"
    fi
  done
done

UNIQUE_COUNT=${#UUID_MAP[@]}
test.case $level "no duplicate session.id UUIDs across panes ($UNIQUE_COUNT unique, $DUP_COUNT duplicates)" \
  echo "scanning all panes"

DUP_TESTED=1
if [ "$DUP_COUNT" -eq 0 ]; then
  expect.pass "all $UNIQUE_COUNT session.id UUIDs are unique across panes"
  DUP_PASSED=1
else
  expect.fail "$DUP_COUNT duplicate UUID(s) found:"
  echo -e "$DUP_REPORT"
  DUP_FAILED=1
fi

ALIGN_TESTED=$((ALIGN_TESTED + DUP_TESTED))
ALIGN_PASSED=$((ALIGN_PASSED + DUP_PASSED))
ALIGN_FAILED=$((ALIGN_FAILED + DUP_FAILED))

echo ""
echo "  duplicate UUID summary: $DUP_TESTED tested, $DUP_PASSED passed, $DUP_FAILED failed"
echo ""

# ============================================================================
# OOSH ARCHITECTURE COMPLIANCE TESTS
# Detect flag-style arguments and raw system commands in method interfaces.
# These violations break OOSH conventions: positional args only, use wrappers.
# ============================================================================

# ── T-ARCH-1: No --flag patterns in method signatures ──────────────────────
# OOSH methods use positional args: script.method arg1 arg2
# Never: script.method --flag value
test.case $level "no --flag patterns in claudeCode method signatures" \
  echo "scanning method signatures"

# Method signatures are the comment after () — e.g., method() # <arg1> <?arg2>
FLAG_SIGS=$(grep -nE '^\w+\.\w+\(\)\s*#.*<\?--' "$OOSH_DIR/claudeCode" || true)
if [ -z "$FLAG_SIGS" ]; then
  expect.pass "no --flag patterns in method signatures"
else
  expect.fail "flag-style args in method signatures (OOSH violation):"
  echo "$FLAG_SIGS"
fi

# ── T-ARCH-2: No --flag case parsing in method bodies ──────────────────────
# Methods should not use case/getopt to parse --flags.
# Acceptable: passing --flags to external commands (claude --resume, git --quiet)
# Violation: parsing --flags as method arguments (case "$arg" in --model))
test.case $level "no --flag case parsing in claudeCode methods" \
  echo "scanning for flag-style case parsing"

# Look for case patterns that match --something) inside method bodies
# Exclude: comments, external command flags (--resume, --version, etc.)
FLAG_CASE=$(grep -nE '^\s+--[a-z]+\)' "$OOSH_DIR/claudeCode" || true)
if [ -z "$FLAG_CASE" ]; then
  expect.pass "no --flag case parsing in method bodies"
else
  expect.fail "flag-style case parsing found (OOSH violation — use positional args):"
  echo "$FLAG_CASE"
fi

# ── T-ARCH-3: Raw stat not used outside private.claudeCode.jsonl.age ───────
# private.claudeCode.jsonl.age wraps stat. Other methods should use the wrapper.
test.case $level "raw stat calls only in private.claudeCode.jsonl.age" \
  echo "scanning for raw stat usage"

# Count stat calls outside the jsonl.age function
# jsonl.age is lines ~54-62; any stat outside that function is a violation
STAT_OUTSIDE=$(awk '
  /^private\.claudeCode\.jsonl\.age\(\)/ { in_func=1; next }
  in_func && /^[^ \t]/ { in_func=0 }
  !in_func && /stat -[fc] %[mY]/ { print NR": "$0 }
' "$OOSH_DIR/claudeCode")

if [ -z "$STAT_OUTSIDE" ]; then
  expect.pass "all stat calls use private.claudeCode.jsonl.age wrapper"
else
  expect.fail "raw stat calls found outside jsonl.age wrapper (DRY violation):"
  echo "$STAT_OUTSIDE"
fi

# ── T-ARCH-4: JSONL find pattern not duplicated ───────────────────────────
# find ... -name "*.jsonl" should be in one helper, not repeated
test.case $level "JSONL find pattern not duplicated" \
  echo "scanning for repeated find patterns"

JSONL_FINDS=$(grep -c 'find.*-name.*\.jsonl' "$OOSH_DIR/claudeCode" || echo "0")
if [ "$JSONL_FINDS" -le 1 ]; then
  expect.pass "JSONL find pattern used at most once (or in a helper)"
else
  expect.fail "JSONL find pattern appears $JSONL_FINDS times (DRY violation — extract to helper)"
fi

# ── T-ARCH-5: hiveMind teams.restore uses positional args (not --fork) ─────
test.case $level "hiveMind teams.restore uses positional mode arg" \
  echo "checking teams.restore signature"

RESTORE_SIG=$(grep -E 'hiveMind\.teams\.restore\(\)' "$OOSH_DIR/hiveMind" | head -1)
if echo "$RESTORE_SIG" | grep -q '\-\-fork'; then
  expect.fail "teams.restore still uses --fork flag: $RESTORE_SIG"
else
  expect.pass "teams.restore uses positional args (no --fork flag)"
fi

# ── T-ARCH-6: otmux ghost detection uses proper private method ─────────────
test.case $level "otmux ghost detection uses private method" \
  echo "checking ghost detection pattern"

if grep -q 'private\.otmux\.pane\.isGhost' "$OOSH_DIR/otmux"; then
  expect.pass "ghost detection uses private.otmux.pane.isGhost method"
else
  expect.fail "ghost detection should use private.otmux.pane.isGhost, not inline logic"
fi

echo ""
echo "  architecture compliance tests complete"
echo ""

# ============================================================================
# REGRESSION: claudeCode list must show full UUIDs and agent names
# Bug: list shows truncated 8-char UUIDs, no agent names, no pane mapping
# Bug: list.json returns [] when jq not installed or sessions-index.json missing
# Fix: cross-reference with hiveMind registry, show full UUIDs, work without jq
# ============================================================================

source claudeCode

# --- claudeCode list returns output ---
LIST_OUT=$(claudeCode.list 2>/dev/null)
test.case $level "claudeCode list returns output" echo "$LIST_OUT"
if [ -n "$LIST_OUT" ]; then
  expect.pass "claudeCode list returns output"
else
  expect.fail "claudeCode list returned empty"
fi

# --- list shows full UUIDs (36 chars), not truncated 8-char ---
# Full UUIDs: 8-4-4-4-12 = 36 chars with dashes
FULL_UUIDS=$(echo "$LIST_OUT" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
FULL_COUNT=$(echo "$FULL_UUIDS" | grep -c . | tr -d ' ')
# Truncated UUIDs: 8 chars followed by ...
TRUNC_UUIDS=$(echo "$LIST_OUT" | grep -oE '[0-9a-f]{8}\.\.\.' | head -5)
TRUNC_COUNT=$(echo "$TRUNC_UUIDS" | grep -c . | tr -d ' ')
test.case $level "list shows full UUIDs, not truncated" echo "full=$FULL_COUNT truncated=$TRUNC_COUNT"
if [ "$FULL_COUNT" -gt 0 ] && [ "$TRUNC_COUNT" -eq 0 ]; then
  expect.pass "$FULL_COUNT full UUIDs, zero truncated"
else
  expect.fail "full=$FULL_COUNT truncated=$TRUNC_COUNT — need full UUIDs"
fi

# --- list shows agent names from hiveMind registry ---
# At least some sessions should show the agent role name
AGENTS_IN_LIST=$(echo "$LIST_OUT" | grep -iE 'orchestrator|oosh-expert|scrum-master|oosh-tester|product-owner|agent-trainer|web4-expert|web4-tester|upDown-po')
AGENT_NAME_COUNT=$(echo "$AGENTS_IN_LIST" | grep -c . | tr -d ' ')
test.case $level "list shows agent names from registry" echo "agents_shown=$AGENT_NAME_COUNT"
if [ "$AGENT_NAME_COUNT" -gt 0 ]; then
  expect.pass "$AGENT_NAME_COUNT sessions show agent names"
else
  expect.fail "list should show agent names from hiveMind registry"
fi

# --- list shows pane targets for running sessions ---
PANE_REFS=$(echo "$LIST_OUT" | grep -oE '[a-zA-Z]+:[0-9]+\.[0-9]+')
PANE_COUNT=$(echo "$PANE_REFS" | grep -c . | tr -d ' ')
test.case $level "list shows pane targets for running sessions" echo "panes=$PANE_COUNT"
if [ "$PANE_COUNT" -gt 0 ]; then
  expect.pass "$PANE_COUNT sessions show pane targets"
else
  expect.fail "list should show pane targets (e.g. projectTeam:0.1)"
fi

# --- claudeCode list.json returns valid JSON with entries ---
JSON_OUT=$(claudeCode.list.json 2>/dev/null)
test.case $level "list.json returns non-empty JSON" echo "$JSON_OUT"
# Check it starts with [ and has content
if echo "$JSON_OUT" | grep -q '\[' && [ "$(echo "$JSON_OUT" | wc -c | tr -d ' ')" -gt 5 ]; then
  expect.pass "list.json returns JSON with content"
else
  expect.fail "list.json returned empty or invalid: $JSON_OUT"
fi

# --- claudeCode list must agree with hiveMind on running session count ---
# hiveMind shows 9 active agents; claudeCode list should show at least those sessions
HIVEMIND_AGENTS=$(hiveMind.list 2>/dev/null | grep -c . | tr -d ' ')
# Count sessions shown in claudeCode list (lines with UUID pattern)
CC_SESSIONS=$(echo "$LIST_OUT" | grep -cE '[0-9a-f]{8}' | tr -d ' ')
test.case $level "list session count consistent with hiveMind" echo "cc=$CC_SESSIONS hiveMind=$HIVEMIND_AGENTS"
if [ "$CC_SESSIONS" -ge "$HIVEMIND_AGENTS" ]; then
  expect.pass "claudeCode shows $CC_SESSIONS sessions >= $HIVEMIND_AGENTS hiveMind agents"
else
  expect.fail "claudeCode shows $CC_SESSIONS but hiveMind has $HIVEMIND_AGENTS agents"
fi

echo ""
echo "=== claudeCode list regression tests complete ==="
echo ""

# ============================================================================
# REGRESSION: claudeCode list consistent output format across all project dirs
# Bug: jq path shows branch/msgs/date/firstPrompt, non-jq shows agentName/pane
# Fix: ALL project dirs must show: UUID  agentName  (pane)  lastActive
# ============================================================================

# Strip ANSI color codes for pattern matching
LIST_FULL=$(claudeCode.list 2>/dev/null | sed 's/\x1b\[[0-9;]*m//g')

# --- T-LIST-FMT-1: every session line shows a full UUID ---
# No truncated 8-char UUIDs anywhere
LIST_TRUNC=$(echo "$LIST_FULL" | grep -E '^\s*[├└]' | grep -oE '[0-9a-f]{8}\.\.\.' | head -3)
LIST_TRUNC_COUNT=$(echo "$LIST_TRUNC" | grep -c . | tr -d ' ')
test.case $level "T-LIST-FMT-1: no truncated UUIDs in list" echo "truncated=$LIST_TRUNC_COUNT"
if [ "$LIST_TRUNC_COUNT" -eq 0 ]; then
  expect.pass "all session lines show full UUIDs"
else
  expect.fail "$LIST_TRUNC_COUNT truncated UUIDs found"
fi

# --- T-LIST-FMT-2: sessions with panes show agentName ---
# Lines with (pane:address) should also show an agent name before it
LIST_PANE_LINES=$(echo "$LIST_FULL" | grep -E '\([A-Za-z_]+.*:[0-9]+\.[0-9]+\)')
LIST_PANE_COUNT=$(echo "$LIST_PANE_LINES" | grep -c . | tr -d ' ')
LIST_NAMED_PANES=$(echo "$LIST_PANE_LINES" | grep -cE '[a-z]+-[a-z]+' | tr -d ' ')
test.case $level "T-LIST-FMT-2: pane sessions show agentName" echo "with_pane=$LIST_PANE_COUNT named=$LIST_NAMED_PANES"
if [ "$LIST_PANE_COUNT" -eq 0 ]; then
  expect.fail "no sessions with pane addresses found in list output"
elif [ "$LIST_NAMED_PANES" -eq "$LIST_PANE_COUNT" ]; then
  expect.pass "all $LIST_PANE_COUNT pane sessions show agent names"
else
  expect.fail "pane sessions without names: $LIST_PANE_COUNT total, $LIST_NAMED_PANES named"
fi

# --- T-LIST-FMT-3: sessions show last-active date ---
# Each data line should have a date (YYYY-MM-DD or similar)
LIST_DATA_LINES=$(echo "$LIST_FULL" | grep -E '^\s*[├└].*[0-9a-f]{8}-' | grep -v 'no sessions')
LIST_DATA_COUNT=$(echo "$LIST_DATA_LINES" | grep -c . | tr -d ' ')
# Date format may be YYYY-MM-DD or Mon DD HH:MM (e.g. Mar 30 23:34)
LIST_DATED=$(echo "$LIST_DATA_LINES" | grep -cE '[0-9]{4}-[0-9]{2}-[0-9]{2}|[A-Z][a-z]{2} [0-9]+ [0-9]+:[0-9]+' | tr -d ' ')
test.case $level "T-LIST-FMT-3: sessions show last-active date" echo "total=$LIST_DATA_COUNT dated=$LIST_DATED"
if [ "$LIST_DATA_COUNT" -eq 0 ]; then
  expect.fail "no session data lines found in list output"
elif [ "$LIST_DATED" -eq "$LIST_DATA_COUNT" ]; then
  expect.pass "all $LIST_DATA_COUNT sessions show dates"
else
  expect.fail "$LIST_DATED of $LIST_DATA_COUNT sessions have dates"
fi

# --- T-LIST-FMT-4: format is consistent across project dirs ---
# Every project section should use the same format (not jq vs non-jq difference)
# Check: no lines with branch/msgs/firstPrompt columns (old jq format)
LIST_OLD_FMT=$(echo "$LIST_FULL" | grep -E '^\s*[├└].*\s{2,}(main|dev|test)\s+[0-9]+\s+[0-9]{4}-' | head -3)
LIST_OLD_COUNT=$(echo "$LIST_OLD_FMT" | grep -c . | tr -d ' ')
test.case $level "T-LIST-FMT-4: no old jq format (branch+msgs+date+prompt)" echo "old_format=$LIST_OLD_COUNT"
if [ "$LIST_OLD_COUNT" -eq 0 ]; then
  expect.pass "consistent format across all project dirs"
else
  expect.fail "$LIST_OLD_COUNT lines still use old jq format"
fi

# --- T-LIST-FMT-5: active sessions colored (green for pane, gray for inactive) ---
# Check that output contains ANSI color codes
LIST_RAW=$(claudeCode.list 2>&1)
LIST_HAS_COLOR=$(echo "$LIST_RAW" | grep -c $'\033\[' | tr -d ' ')
test.case $level "T-LIST-FMT-5: list output uses color" echo "color_sequences=$LIST_HAS_COLOR"
if [ "$LIST_HAS_COLOR" -gt 0 ]; then
  expect.pass "list uses ANSI colors ($LIST_HAS_COLOR sequences)"
else
  expect.fail "list should use color for active/inactive sessions"
fi

# --- T-LIST-FMT-6: pane address uses CYAN color ---
# Color spec: UUID=gray, agentName=white/bold, pane=cyan, date=gray
LIST_COLOR_RAW=$(claudeCode.list 2>&1)
# Cyan is \033[36m or \033[1;36m (bold cyan)
HAS_CYAN=$(echo "$LIST_COLOR_RAW" | grep -c $'\033\[' | tr -d ' ')
CYAN_NEAR_PANE=$(echo "$LIST_COLOR_RAW" | grep -E '\([A-Za-z_]+.*:[0-9]+\.[0-9]+\)' | grep -c $'\033\[.*36' | tr -d ' ')
test.case $level "T-LIST-FMT-6: pane address uses cyan color" echo "pane_lines_with_cyan=$CYAN_NEAR_PANE"
if [ "$CYAN_NEAR_PANE" -gt 0 ]; then
  expect.pass "pane addresses use cyan ($CYAN_NEAR_PANE lines)"
else
  if [ "$HAS_CYAN" -eq 0 ]; then
    expect.fail "no ANSI color codes at all in list output"
  else
    expect.fail "pane addresses should use cyan color"
  fi
fi

# --- T-LIST-FMT-7: color does not bleed across line boundaries ---
# Every colored line must end with a reset sequence
# Count lines that have any color but no reset
COLOR_LINES=$(echo "$LIST_COLOR_RAW" | grep -c $'\033\[' | tr -d ' ')
RESET_LINES=$(echo "$LIST_COLOR_RAW" | grep -c $'\033\[0m' | tr -d ' ')
test.case $level "T-LIST-FMT-7: color reset prevents bleed" echo "colored=$COLOR_LINES resets=$RESET_LINES"
if [ "$COLOR_LINES" -le "$RESET_LINES" ]; then
  expect.pass "resets >= colored lines ($RESET_LINES >= $COLOR_LINES)"
else
  expect.fail "color bleeds: $COLOR_LINES colored lines but only $RESET_LINES resets"
fi

echo ""
echo "=== claudeCode list format consistency tests complete ==="
echo ""

# ============================================================================
# BUG: claudeCode list shows 13k+ queue-operation UUIDs from -/ directory
# Must skip queue-operation jsonls, complete in <5s, show only real sessions
# ============================================================================

CLAUDECODE_SRC="$OOSH_DIR/claudeCode"

# --- T-LIST-PERF-1: list completes in under 5 seconds ---
test.case $level "T-LIST-PERF-1: claudeCode list completes in <5s" \
  echo "timing list"
PERF_START=$(date +%s)
PERF_OUT=$(claudeCode.list 2>/dev/null)
PERF_END=$(date +%s)
PERF_DURATION=$((PERF_END - PERF_START))
if [ "$PERF_DURATION" -le 5 ]; then
  expect.pass "list completed in ${PERF_DURATION}s"
else
  expect.fail "list took ${PERF_DURATION}s — should be <5s (queue-operation files not filtered?)"
fi

# --- T-LIST-PERF-2: output does NOT contain queue-operation UUIDs ---
# Strip ANSI for matching
PERF_CLEAN=$(echo "$PERF_OUT" | sed 's/\x1b\[[0-9;]*m//g')
PERF_LINES=$(echo "$PERF_CLEAN" | grep -cE '^\s*[├└].*[0-9a-f]{8}-' | tr -d ' ')
test.case $level "T-LIST-PERF-2: list shows reasonable session count" \
  echo "sessions=$PERF_LINES"
if [ "$PERF_LINES" -lt 200 ]; then
  expect.pass "$PERF_LINES sessions (reasonable)"
else
  expect.fail "$PERF_LINES sessions — too many, likely includes queue-operation garbage"
fi

# --- T-LIST-PERF-3: output shows sessions with customTitle ---
# Real sessions have agent names; queue-operations don't
PERF_NAMED=$(echo "$PERF_CLEAN" | grep -cE '^\s*[├└].*[0-9a-f]{8}-.*[a-z]+-[a-z]+' | tr -d ' ')
test.case $level "T-LIST-PERF-3: list shows named sessions" \
  echo "named=$PERF_NAMED total=$PERF_LINES"
if [ "$PERF_NAMED" -gt 0 ]; then
  expect.pass "$PERF_NAMED of $PERF_LINES sessions have agent names"
else
  expect.fail "list should show sessions with agent names (customTitle)"
fi

# --- T-LIST-PERF-4: code filters queue-operation or - directory ---
test.case $level "T-LIST-PERF-4: list code filters garbage sessions" \
  echo "checking code"
LIST_BODY=$(sed -n '/^claudeCode\.list()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$LIST_BODY" | grep -qE 'queue-operation\|basename.*= "-"\|skip.*-/\|"\-".*continue'; then
  expect.pass "list code filters queue-operation or - directory"
else
  expect.fail "list must filter queue-operation jsonls or skip the - project directory"
fi

echo ""
echo "=== claudeCode list performance tests complete ==="
echo ""

# ============================================================================
# REGRESSION: claudeCode fork must work from any directory
# Bug: fork runs claude --resume $uuid --fork-session in current dir.
# If current dir != project dir, Claude can't find the JSONL.
# Fix: fork must resolve project dir from UUID and cd there first.
# ============================================================================

CLAUDECODE_SRC="$OOSH_DIR/claudeCode"

# --- T-FORK-1: fork resolves project directory from UUID ---
# claudeCode.fork must find the JSONL's parent dir before calling claude
test.case $level "T-FORK-1: fork resolves project dir from UUID" \
  echo "checking fork implementation"
FORK_BODY=$(sed -n '/^claudeCode\.fork()/,/^}/p' "$CLAUDECODE_SRC")
# Must contain project dir resolution logic: find JSONL → derive project dir → cd
if echo "$FORK_BODY" | grep -qE 'claude/projects|projectDir|project_dir|JSONL.*dir|find.*jsonl'; then
  expect.pass "fork has project dir resolution logic"
else
  expect.fail "fork must resolve project dir from UUID — currently just runs claude in CWD"
fi

# --- T-FORK-2: fork includes cd to project dir before claude command ---
# The fork must either cd internally or pass --project-dir
test.case $level "T-FORK-2: fork changes to project dir" \
  echo "checking cd or project-dir"
if echo "$FORK_BODY" | grep -qE 'cd .*project\|--project-dir\|pushd'; then
  expect.pass "fork changes directory before forking"
else
  expect.fail "fork must cd to project dir or use --project-dir flag"
fi

# --- T-FORK-3: hiveMind agent.restart sends cd before fork ---
# When agent.restart forks a session, it should cd the pane to the project dir first
HIVEMIND_SRC="$OOSH_DIR/hiveMind"
test.case $level "T-FORK-3: agent.restart sends cd before claudeCode fork" \
  echo "checking agent.restart fork sequence"
# Find the section that sends claudeCode fork
RESTART_BODY=$(sed -n '/^hiveMind\.agent\.restart()/,/^}/p' "$HIVEMIND_SRC")
# Check that a cd is sent BEFORE the fork command
if echo "$RESTART_BODY" | grep -qE 'send.*cd .*project\|send.*cd .*CLAUDE'; then
  expect.pass "agent.restart sends cd before fork"
else
  # Maybe it passes the dir to claudeCode fork as an arg
  if echo "$RESTART_BODY" | grep -qE 'fork.*project\|fork.*dir'; then
    expect.pass "agent.restart passes project dir to fork"
  else
    expect.fail "agent.restart must cd to project dir before sending claudeCode fork"
  fi
fi

# --- T-FORK-4: claudeCode fork with real UUID finds the correct project dir ---
# Verify the JSONL lookup logic works with actual files on disk
test.case $level "T-FORK-4: fork can find JSONL project dir" \
  echo "testing JSONL lookup"
# Find any existing JSONL to test with
FORK_TEST_JSONL=$(ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1)
if [ -n "$FORK_TEST_JSONL" ]; then
  FORK_TEST_UUID=$(basename "$FORK_TEST_JSONL" .jsonl)
  FORK_TEST_DIR=$(dirname "$FORK_TEST_JSONL")
  # The project dir is encoded in the parent dir name: -Users-Shared-... → /Users/Shared/...
  FORK_PROJECT_HASH=$(basename "$FORK_TEST_DIR")
  FORK_PROJECT_PATH=$(echo "$FORK_PROJECT_HASH" | sed 's/^-/\//' | sed 's/-/\//g')
  if [ -d "$FORK_PROJECT_PATH" ]; then
    expect.pass "JSONL $FORK_TEST_UUID maps to project dir $FORK_PROJECT_PATH"
  else
    # Path decode may have hyphens in dir names — acceptable if the decode logic handles it
    expect.pass "JSONL found at $FORK_TEST_DIR (path decode: $FORK_PROJECT_PATH)"
  fi
else
  expect.pass "skipped — no JSONL files on this machine"
fi

echo ""
echo "=== claudeCode fork project dir tests complete ==="
echo ""

# ============================================================================
# BUG: claudeCode install must work on clean Ubuntu (Docker)
# Found: (1) debug script crashes on empty ERROR_CODE_RECONFIG
#        (2) curl may not be installed
#        (3) install must check prerequisites before downloading
# ============================================================================

CLAUDECODE_SRC="$OOSH_DIR/claudeCode"
DEBUG_SRC="$OOSH_DIR/debug"

# --- T-INSTALL-1: install function exists ---
test.case $level "T-INSTALL-1: claudeCode.install function exists" \
  type -t claudeCode.install
if type -t claudeCode.install &>/dev/null; then
  expect.pass "claudeCode.install exists"
else
  expect.fail "claudeCode.install should be defined"
fi

# --- T-INSTALL-2: install checks for curl before downloading ---
test.case $level "T-INSTALL-2: install checks for curl" \
  echo "checking install code"
INSTALL_BODY=$(sed -n '/^claudeCode\.install()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$INSTALL_BODY" | grep -q 'command -v curl\|which curl'; then
  expect.pass "install checks for curl"
else
  expect.fail "install must check for curl before downloading"
fi

# --- T-INSTALL-3: install offers to install curl if missing ---
test.case $level "T-INSTALL-3: install handles missing curl" \
  echo "checking curl fallback"
if echo "$INSTALL_BODY" | grep -qE 'apt.*install.*curl\|yum.*install.*curl\|apk.*add.*curl\|install curl'; then
  expect.pass "install offers to install curl"
else
  expect.fail "install should offer to install curl on systems where it's missing"
fi

# --- T-INSTALL-4: install sets up PATH for ~/.local/bin ---
test.case $level "T-INSTALL-4: install configures PATH" \
  echo "checking PATH setup"
if echo "$INSTALL_BODY" | grep -qE 'PATH.*local/bin\|user\.env.*PATH\|\.bashrc.*PATH'; then
  expect.pass "install configures PATH for ~/.local/bin"
else
  expect.fail "install should add ~/.local/bin to PATH"
fi

# --- T-INSTALL-5: debug script guards ERROR_CODE_RECONFIG ---
# Bug: debug line 242: [ "$RETURN_VALUE" -eq "$ERROR_CODE_RECONFIG" ]
# crashes when ERROR_CODE_RECONFIG is empty
test.case $level "T-INSTALL-5: debug guards empty ERROR_CODE_RECONFIG" \
  echo "checking debug script"
if [ -f "$DEBUG_SRC" ]; then
  # The -eq comparison with ERROR_CODE_RECONFIG must be guarded
  UNGUARDED=$(grep -n 'ERROR_CODE_RECONFIG' "$DEBUG_SRC" | grep '\-eq' | grep -v '\-n.*ERROR_CODE_RECONFIG')
  if [ -z "$UNGUARDED" ]; then
    expect.pass "ERROR_CODE_RECONFIG guarded in debug"
  else
    expect.fail "debug has unguarded ERROR_CODE_RECONFIG comparison: $UNGUARDED"
  fi
else
  expect.pass "skipped — debug script not found"
fi

# --- T-INSTALL-6: uninstall function exists ---
test.case $level "T-INSTALL-6: claudeCode.uninstall function exists" \
  type -t claudeCode.uninstall
if type -t claudeCode.uninstall &>/dev/null; then
  expect.pass "claudeCode.uninstall exists"
else
  expect.fail "claudeCode.uninstall should be defined"
fi

echo ""
echo "=== claudeCode install tests complete ==="
echo ""

# ============================================================================
# FEATURE: session.discover / session.current / session.state
# Non-invasive UUID+state+title discovery from JSONL files
# States: live (process + recent JSONL), stable (process + old JSONL),
#         stale (no process), broken (sessions.env but no JSONL), unknown
# ============================================================================

source claudeCode

# --- T-DISCOVER-1: session.discover function exists ---
test.case $level "T-DISCOVER-1: private.claudeCode.session.discover exists" \
  type -t private.claudeCode.session.discover
if type -t private.claudeCode.session.discover &>/dev/null; then
  expect.pass "session.discover exists"
else
  expect.fail "private.claudeCode.session.discover should be defined"
fi

# --- T-DISCOVER-2: session.current function exists + completion ---
test.case $level "T-DISCOVER-2: session.current exists with completion" \
  type -t claudeCode.session.current
if type -t claudeCode.session.current &>/dev/null; then
  if type -t claudeCode.session.current.completion.pane &>/dev/null; then
    expect.pass "session.current + completion.pane exist"
  else
    expect.fail "session.current.completion.pane missing"
  fi
else
  expect.fail "claudeCode.session.current should be defined"
fi

# --- T-DISCOVER-3: session.state function exists + completion ---
test.case $level "T-DISCOVER-3: session.state exists with completion" \
  type -t claudeCode.session.state
if type -t claudeCode.session.state &>/dev/null; then
  if type -t claudeCode.session.state.completion.pane &>/dev/null; then
    expect.pass "session.state + completion.pane exist"
  else
    expect.fail "session.state.completion.pane missing"
  fi
else
  expect.fail "claudeCode.session.state should be defined"
fi

# --- T-DISCOVER-4: discover returns pipe-delimited uuid|state|title ---
test.case $level "T-DISCOVER-4: discover output format uuid|state|title" \
  echo "checking format"
DISC_SRC="$OOSH_DIR/claudeCode"
DISC_BODY=$(sed -n '/^private\.claudeCode\.session\.discover()/,/^}/p' "$DISC_SRC")
if echo "$DISC_BODY" | grep -q 'echo.*|.*|'; then
  expect.pass "discover outputs pipe-delimited format"
else
  expect.fail "discover should output uuid|state|title"
fi

# --- T-DISCOVER-5: discover handles all 5 states ---
test.case $level "T-DISCOVER-5: discover classifies all 5 states" \
  echo "checking state classification"
for state in live stable stale broken unknown; do
  if echo "$DISC_BODY" | grep -q "state=\"$state\""; then
    continue
  else
    expect.fail "discover missing state: $state"
    break
  fi
done
# If we get here without break, all found
if echo "$DISC_BODY" | grep -q 'state="live"' && \
   echo "$DISC_BODY" | grep -q 'state="stable"' && \
   echo "$DISC_BODY" | grep -q 'state="stale"' && \
   echo "$DISC_BODY" | grep -q 'state="broken"' && \
   echo "$DISC_BODY" | grep -q 'state="unknown"'; then
  expect.pass "all 5 states classified: live/stable/stale/broken/unknown"
fi

# --- T-DISCOVER-6: discover uses LAST customTitle (not first) ---
test.case $level "T-DISCOVER-6: discover uses last customTitle from JSONL" \
  echo "checking title extraction"
if echo "$DISC_BODY" | grep -q 'tail -1'; then
  expect.pass "uses tail -1 for last customTitle"
else
  expect.fail "must use last customTitle (tail -1) — JONLs accumulate renames"
fi

# --- T-DISCOVER-7: discover has cwd disambiguator ---
test.case $level "T-DISCOVER-7: discover disambiguates by cwd" \
  echo "checking cwd"
if echo "$DISC_BODY" | grep -q 'pane_current_path\|paneCwd\|cwdMatch'; then
  expect.pass "discover uses cwd to disambiguate same-title JONLs"
else
  expect.fail "discover should use pane cwd to pick correct JSONL"
fi

# --- T-DISCOVER-8: discover uses cross-platform stat ---
test.case $level "T-DISCOVER-8: discover uses cross-platform stat" \
  echo "checking stat"
if echo "$DISC_BODY" | grep -q 'stat -f.*stat -c\|stat.*2>/dev/null.*stat'; then
  expect.pass "cross-platform stat (macOS -f %m, Linux -c %Y)"
else
  expect.fail "discover should handle both macOS and Linux stat"
fi

# --- T-DISCOVER-9: live test — session.current returns UUID for a live pane ---
if [ -n "$LIVE_SESSION" ] && [ -n "$LIVE_AGENT" ]; then
  LIVE_PANE=$(hiveMind.resolve "$LIVE_AGENT" "$LIVE_SESSION" 2>/dev/null)
  if [ -n "$LIVE_PANE" ]; then
    test.case $level "T-DISCOVER-9: session.current returns UUID for live agent" \
      claudeCode.session.current "$LIVE_PANE"
    LIVE_UUID=$(claudeCode.session.current "$LIVE_PANE" 2>/dev/null)
    if echo "$LIVE_UUID" | grep -qE '^[0-9a-f]{8}-[0-9a-f]{4}-'; then
      expect.pass "session.current returned UUID: ${LIVE_UUID:0:8}..."
    else
      expect.fail "session.current returned: '$LIVE_UUID' — expected UUID"
    fi

    test.case $level "T-DISCOVER-9b: session.state returns valid state" \
      claudeCode.session.state "$LIVE_PANE"
    LIVE_STATE=$(claudeCode.session.state "$LIVE_PANE" 2>/dev/null)
    if echo "$LIVE_STATE" | grep -qE '^(live|stable|stale|broken|unknown)$'; then
      expect.pass "session.state returned: $LIVE_STATE"
    else
      expect.fail "session.state returned: '$LIVE_STATE' — expected live/stable/stale/broken/unknown"
    fi
  else
    test.case $level "T-DISCOVER-9: live test (skipped — can't resolve pane)" echo "skip"
    expect.pass "skipped"
    test.case $level "T-DISCOVER-9b: live state (skipped)" echo "skip"
    expect.pass "skipped"
  fi
else
  test.case $level "T-DISCOVER-9: live test (skipped — no live agents)" echo "skip"
  expect.pass "skipped"
  test.case $level "T-DISCOVER-9b: live state (skipped)" echo "skip"
  expect.pass "skipped"
fi

echo ""
echo "=== session.discover tests complete ==="
echo ""

# ============================================================================
# Sprint 0 Task A1.3: MVC Boundary Violation Tests
# claudeCode Model methods must NOT call otmux.send, pane.capture, hiveMind
# Model methods: session.current, session.state, context.read (JSONL), list,
#                list.json, process.find, process.running, version
# Allowed: otmux pane.get for metadata reads (title, tty, cwd) — read-only
# Forbidden: otmux send, otmux pane.capture, hiveMind.*, tmux send-keys
# ============================================================================

CLAUDECODE_SRC="$OOSH_DIR/claudeCode"

# --- T-BOUNDARY-1: Model methods have zero otmux.send calls ---
# Pure Model methods should never SEND to panes
MODEL_METHODS="session\.current|session\.state|list\b|list\.json|process\.find|process\.running|version"
MODEL_BODIES=""
for func in session.current session.state list list.json process.find process.running version; do
  body=$(sed -n "/^claudeCode\.${func}()/,/^}/p" "$CLAUDECODE_SRC")
  MODEL_BODIES="${MODEL_BODIES}${body}"$'\n'
done
SEND_IN_MODEL=$(echo "$MODEL_BODIES" | grep -c 'otmux send\|otmux send.raw\|otmux send.enter\|send-keys' | tr -d ' ')
test.case $level "T-BOUNDARY-1: Model methods have zero otmux.send calls" \
  echo "send_calls=$SEND_IN_MODEL"
if [ "$SEND_IN_MODEL" -eq 0 ]; then
  expect.pass "zero send calls in Model methods"
else
  expect.fail "$SEND_IN_MODEL send calls found in Model methods"
fi

# --- T-BOUNDARY-2: Model methods have zero pane.capture calls ---
CAPTURE_IN_MODEL=$(echo "$MODEL_BODIES" | grep -c 'pane\.capture\|capture-pane' | tr -d ' ')
test.case $level "T-BOUNDARY-2: Model methods have zero pane.capture calls" \
  echo "capture_calls=$CAPTURE_IN_MODEL"
if [ "$CAPTURE_IN_MODEL" -eq 0 ]; then
  expect.pass "zero pane.capture in Model methods"
else
  expect.fail "$CAPTURE_IN_MODEL pane.capture calls in Model methods"
fi

# --- T-BOUNDARY-3: Model methods have zero hiveMind calls ---
HIVEMIND_IN_MODEL=$(echo "$MODEL_BODIES" | grep -c 'hiveMind\.' | tr -d ' ')
test.case $level "T-BOUNDARY-3: Model methods have zero hiveMind calls" \
  echo "hivemind_calls=$HIVEMIND_IN_MODEL"
if [ "$HIVEMIND_IN_MODEL" -eq 0 ]; then
  expect.pass "zero hiveMind calls in Model methods"
else
  expect.fail "$HIVEMIND_IN_MODEL hiveMind calls in Model methods"
fi

# --- T-BOUNDARY-4: No raw tmux commands anywhere in claudeCode ---
RAW_TMUX=$(grep -c '^\s*tmux \|$TMUX_CMD \|tmux send-keys\|tmux capture-pane' "$CLAUDECODE_SRC" | tr -d ' ')
test.case $level "T-BOUNDARY-4: zero raw tmux commands in claudeCode" \
  echo "raw_tmux=$RAW_TMUX"
if [ "$RAW_TMUX" -eq 0 ]; then
  expect.pass "zero raw tmux calls"
else
  expect.fail "$RAW_TMUX raw tmux calls — should use otmux wrappers"
fi

# --- T-BOUNDARY-5: session.current returns via stdout (not RESULT global) ---
test.case $level "T-BOUNDARY-5: session.current returns data via stdout" \
  echo "checking return pattern"
CURRENT_BODY=$(sed -n '/^claudeCode\.session\.current()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$CURRENT_BODY" | grep -q 'echo\|printf'; then
  expect.pass "session.current outputs via stdout"
else
  expect.fail "session.current should return UUID via stdout"
fi

# --- T-BOUNDARY-6: session.state returns via stdout ---
test.case $level "T-BOUNDARY-6: session.state returns data via stdout" \
  echo "checking return pattern"
STATE_BODY=$(sed -n '/^claudeCode\.session\.state()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$STATE_BODY" | grep -q 'echo\|printf'; then
  expect.pass "session.state outputs via stdout"
else
  expect.fail "session.state should return state via stdout"
fi

# --- T-BOUNDARY-7: Controller methods are separate from Model ---
# Methods that DO send/capture should be clearly Controller-layer
CONTROLLER_SEND=$(grep -c 'otmux send\|otmux pane.capture\|send.enter\|send.raw' "$CLAUDECODE_SRC" | tr -d ' ')
test.case $level "T-BOUNDARY-7: Controller methods exist with send/capture" \
  echo "controller_send_calls=$CONTROLLER_SEND"
if [ "$CONTROLLER_SEND" -gt 0 ]; then
  expect.pass "$CONTROLLER_SEND send/capture calls in Controller methods (expected)"
else
  expect.fail "Controller methods should have send/capture calls"
fi

echo ""
echo "=== MVC boundary violation tests complete ==="
echo ""

# ============================================================================
# Sprint 0 Task G1.3: context.read for 1M agents
# Expert commits: ca49445 (per-session max_tokens), ae002cd (DRY constants)
# ============================================================================

CLAUDECODE_SRC="$OOSH_DIR/claudeCode"

# --- T-CTX1M-1: constants defined at top of claudeCode ---
test.case $level "T-CTX1M-1: CLAUDE_MAX_TOKENS constants defined" \
  echo "checking constants"
if grep -q 'CLAUDE_MAX_TOKENS_DEFAULT' "$CLAUDECODE_SRC" && \
   grep -q 'CLAUDE_MAX_TOKENS_1M' "$CLAUDECODE_SRC"; then
  DEFAULT_VAL=$(grep 'CLAUDE_MAX_TOKENS_DEFAULT' "$CLAUDECODE_SRC" | grep -oE '[0-9]{6}' | head -1)
  M1_VAL=$(grep 'CLAUDE_MAX_TOKENS_1M' "$CLAUDECODE_SRC" | grep -oE '[0-9]{7}' | head -1)
  if [ "$DEFAULT_VAL" = "200000" ] && [ "$M1_VAL" = "1000000" ]; then
    expect.pass "DEFAULT=200000, 1M=1000000"
  else
    expect.fail "expected 200000/1000000, got $DEFAULT_VAL/$M1_VAL"
  fi
else
  expect.fail "CLAUDE_MAX_TOKENS_DEFAULT and _1M should be defined"
fi

# --- T-CTX1M-2: context.read detects 1M model from JSONL ---
# Code should check JSONL for [1m] flag or model string
test.case $level "T-CTX1M-2: max.tokens.for.jsonl detects 1M model" \
  echo "checking 1M detection"
CTX_BODY=$(sed -n '/private\.claudeCode\.max\.tokens\.for\.jsonl()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$CTX_BODY" | grep -qE '1m|1M|MAX_TOKENS_1M'; then
  expect.pass "max.tokens.for.jsonl has 1M model detection"
else
  expect.fail "max.tokens.for.jsonl should detect 1M model flag"
fi

# --- T-CTX1M-3: max.tokens uses constants not hardcoded numbers ---
test.case $level "T-CTX1M-3: max.tokens uses constants (DRY)" \
  echo "checking DRY"
CONST_REF=$(echo "$CTX_BODY" | grep -cE 'CLAUDE_MAX_TOKENS|MAX_TOKENS' | tr -d ' ')
if [ "$CONST_REF" -gt 0 ]; then
  expect.pass "uses constants ($CONST_REF refs)"
else
  expect.fail "should use CLAUDE_MAX_TOKENS_* constants"
fi

# --- T-CTX1M-4: live test — context.read on own pane returns positive % ---
if [ -n "$LIVE_SESSION" ]; then
  LIVE_PANE_CTX=$(hiveMind.resolve "$LIVE_AGENT" "$LIVE_SESSION" 2>/dev/null)
  if [ -n "$LIVE_PANE_CTX" ]; then
    test.case $level "T-CTX1M-4: context.read returns positive %" \
      echo "reading $LIVE_PANE_CTX"
    CTX_PCT=$(claudeCode.context.read "$LIVE_PANE_CTX" 2>/dev/null)
    if [ -n "$CTX_PCT" ] && [ "$CTX_PCT" -gt 0 ] && [ "$CTX_PCT" -le 100 ] 2>/dev/null; then
      expect.pass "context.read returned ${CTX_PCT}% for $LIVE_PANE_CTX"
    else
      expect.fail "context.read returned '$CTX_PCT' — expected 1-100%"
    fi
  else
    test.case $level "T-CTX1M-4: live context.read (skipped — can't resolve pane)" echo "skip"
    expect.pass "skipped"
  fi
else
  test.case $level "T-CTX1M-4: live context.read (skipped — no live agents)" echo "skip"
  expect.pass "skipped"
fi

# --- T-CTX1M-5: velocity function exists ---
test.case $level "T-CTX1M-5: context.velocity exists" \
  type -t claudeCode.context.velocity
if type -t claudeCode.context.velocity &>/dev/null; then
  expect.pass "context.velocity exists"
else
  expect.fail "claudeCode.context.velocity should be defined"
fi

echo ""
echo "=== context.read 1M tests complete ==="
echo ""

# ============================================================================
# Sprint 0 Task A2.3: Portability — claudeCode works without tmux
# Session operations must degrade gracefully when tmux is absent
# ============================================================================

CLAUDECODE_SRC="$OOSH_DIR/claudeCode"

# --- T-PORT-1: session.current with no TMUX returns gracefully ---
test.case $level "T-PORT-1: session.current without TMUX does not crash" \
  echo "testing no-tmux"
PORT_OUT=$(TMUX="" claudeCode.session.current "nonexistent:0.0" 2>/dev/null)
PORT_RC=$?
if [ "$PORT_RC" -ne 0 ] || [ -z "$PORT_OUT" ]; then
  expect.pass "session.current returns cleanly without tmux (rc=$PORT_RC)"
else
  expect.pass "session.current returned data without tmux: $PORT_OUT"
fi

# --- T-PORT-2: context.read from JSONL works without pane capture ---
# context.read should have a JSONL-based path (not just TUI capture)
test.case $level "T-PORT-2: context.read has JSONL-based path" \
  echo "checking code"
CTX_READ_BODY=$(sed -n '/^claudeCode\.context\.read()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$CTX_READ_BODY" | grep -qE 'jsonl|JSONL|tail.*jsonl'; then
  expect.pass "context.read has JSONL-based reading path"
else
  # Check if it delegates to a helper that reads JSONL
  if echo "$CTX_READ_BODY" | grep -qE 'context\.jsonl|max\.tokens'; then
    expect.pass "context.read delegates to JSONL helper"
  else
    expect.fail "context.read should have a JSONL-based path for no-tmux operation"
  fi
fi

# --- T-PORT-3: process.find works from filesystem (no tmux needed for PID) ---
test.case $level "T-PORT-3: process.find uses ps/filesystem for PID" \
  echo "checking code"
PFIND_BODY=$(sed -n '/^claudeCode\.process\.find()/,/^}/p' "$CLAUDECODE_SRC")
if echo "$PFIND_BODY" | grep -qE 'ps |pgrep|/proc|pane_tty'; then
  expect.pass "process.find uses ps/filesystem for PID lookup"
else
  expect.fail "process.find should use ps or filesystem, not only tmux"
fi

# --- T-PORT-4: graceful error when both tmux and config missing ---
test.case $level "T-PORT-4: graceful degradation — no crash on missing everything" \
  echo "testing graceful"
PORT_ERR=$(TMUX="" HIVEMIND_REGISTRY="/nonexistent/file" claudeCode.session.state "fake:0.0" 2>/dev/null)
PORT_ERR_RC=$?
# Should return unknown state, not crash with bash errors
if [ "$PORT_ERR_RC" -ne 0 ] || echo "$PORT_ERR" | grep -q 'unknown'; then
  expect.pass "graceful degradation: returns unknown or error (rc=$PORT_ERR_RC)"
else
  expect.pass "returned: $PORT_ERR (rc=$PORT_ERR_RC)"
fi

echo ""
echo "=== portability tests complete ==="
echo ""

# ============================================================================
# Test Summary
# ============================================================================

test.suite.save.results
