#!/usr/bin/env bash
# ===========================================================================
# run_loop.sh — Autonomous Planning, Execution, and Peer-Review Orchestrator
# ===========================================================================

set -euo pipefail

# B-13: the arg parser below consumes "$@" (shift), so capture argv now —
# the freeze re-exec needs the original arguments (measured: $#=0 after parse).
MAM_LOOP_ARGV=("$@")

# 1. Load Common Framework Library
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
MAM_REAL_ROOT="${MAM_REAL_ROOT:-$REPO_ROOT}"
# shellcheck disable=SC1091
source "$REPO_ROOT/.agents/skills/lib.sh"
# shellcheck disable=SC1091
source "$REPO_ROOT/.agents/skills/multi-agent-mux-loop/scripts/diff_collect.sh"
# shellcheck disable=SC1091
source "$REPO_ROOT/.agents/skills/multi-agent-mux-loop/scripts/loop_lock.sh"

# Default configuration parameters
PLAN_MODE=false
PLAN_TALK_TURNS=1
ALL_REVIEWERS=false
MAX_LOOP=3
MAX_REBUT=1
VERBOSE=false
CLEANUP=false
TARGET_AGENT=""
TASK=""
REVIEWER_LIST=""
PLANNER_SESSION_OVERRIDE=""

# Print usage instructions
usage() {
  echo "Usage: $0 [options] --creator <agent-session-name> --task <goal-text>"
  echo "Options:"
  echo "  --creator <name>       Creator session that implements the task (required)"
  echo "  --plan                 Enable Planner agent intervention & design phase"
  echo "  --planner <name>       Planner session (requires --plan; default: auto-resolve)"
  echo "  --plan-talk N          Planner-Creator discussion limit turns (default: 1)"
  echo "  --reviewer \"A,B\"       Targeted reviewer session name list (comma-separated)"
  echo "  --all-reviewer         Enforce PASS verdict from all active reviewer sessions"
  echo "  --max-loop N           Max execution-review corrective loop runs (default: 3)"
  echo "  --max-rebut N          Max rebuttal attempts per review iteration (default: 1, 0: disabled)"
  echo "  --verbose              Print detailed execution timeline traces"
  echo "  --cleanup              Purge temporary job directories upon success"
  exit 1
}

# Parse options safely
while [[ "$#" -gt 0 ]]; do
  case "$1" in
    --plan) PLAN_MODE=true; shift ;;
    --plan-talk)
      if [[ ! "$2" =~ ^[0-9]+$ ]]; then
        echo "ERROR: --plan-talk requires a positive integer."
        exit 1
      fi
      PLAN_TALK_TURNS="$2"; shift 2 ;;
    --reviewer) REVIEWER_LIST="$2"; shift 2 ;;
    --all-reviewer) ALL_REVIEWERS=true; shift ;;
    --max-loop)
      if [[ ! "$2" =~ ^[0-9]+$ ]] || [ "$2" -le 0 ]; then
        echo "ERROR: --max-loop requires a positive non-zero integer."
        exit 1
      fi
      MAX_LOOP="$2"; shift 2 ;;
    --max-rebut)
      if [[ ! "$2" =~ ^[0-9]+$ ]]; then
        echo "ERROR: --max-rebut requires a non-negative integer."
        exit 1
      fi
      MAX_REBUT="$2"; shift 2 ;;
    --verbose) VERBOSE=true; shift ;;
    --cleanup) CLEANUP=true; shift ;;
    --creator) TARGET_AGENT="$2"; shift 2 ;;
    --target-agent)
      echo "ERROR: --target-agent was removed. Use --creator <session> instead." >&2
      exit 1
      ;;
    --planner) PLANNER_SESSION_OVERRIDE="$2"; shift 2 ;;
    --task) TASK="$2"; shift 2 ;;
    -h|--help) usage ;;
    *) echo "Unknown option: $1"; usage ;;
  esac
done

# Run-wide cap on rebuttal rounds. Makes the bound explicit and enforced
# rather than merely emergent from MAX_LOOP x MAX_REBUT.
REBUT_TOTAL_BUDGET=$((MAX_REBUT * MAX_LOOP))

if [ -z "$TARGET_AGENT" ] || [ -z "$TASK" ]; then
  echo "ERROR: --creator and --task are mandatory fields." >&2
  usage
fi

if [ -n "${PLANNER_SESSION_OVERRIDE:-}" ] && [ "$PLAN_MODE" = false ]; then
  echo "ERROR: --planner was specified without --plan." >&2
  echo "To enable planning, include the --plan flag:" >&2
  echo "  run_loop.sh --creator <creator> --plan --planner <planner> --task \"...\"" >&2
  exit 1
fi

# --- B-13 Stage 2: freeze the runtime before the loop can be edited under us ---
# bash keeps reading a running script from disk by byte offset, so a worker that
# edits .agents/skills/ mid-loop can break this very file (measured: even a valid
# replacement died with "unexpected EOF"). Re-exec once from a snapshot.
# NOTE: log_* are not defined until :114 — use echo here, not log_warn (C2).
if [ -z "${MAM_LOOP_FREEZE_DIR:-}" ] && [ "${MAM_LOOP_NO_FREEZE:-0}" != "1" ]; then
  _freeze=""
  _freeze="$(mktemp -d "${TMPDIR:-/tmp}/mam-loop-freeze.XXXXXX" 2>/dev/null)" || _freeze=""
  if [ -n "$_freeze" ] && mkdir -p "$_freeze/.agents" 2>/dev/null \
     && cp -R "$REPO_ROOT/.agents/skills" "$_freeze/.agents/skills" 2>/dev/null; then
    export MAM_LOOP_FREEZE_DIR="$_freeze"
    export MAM_LOOP_FREEZE_OWNED="1"          # ← C1: cleanup gate requires this
    export MAM_REAL_ROOT="$REPO_ROOT"
    export WORKSPACE_ROOT="$REPO_ROOT"
    [ -f "$REPO_ROOT/.mam.env" ] && export MAM_ENV_FILE="$REPO_ROOT/.mam.env"
    exec bash "$_freeze/.agents/skills/multi-agent-mux-loop/scripts/run_loop.sh" \
      ${MAM_LOOP_ARGV[@]+"${MAM_LOOP_ARGV[@]}"}    # ← P1: original argv (bash 3.2 guarded)
  fi
  [ -n "$_freeze" ] && rm -rf "$_freeze"
  echo -e "\033[1;33m[!]\033[0m freeze snapshot failed — continuing unfrozen (B-13 protection off)" >&2
fi

MAM_LOOP_MARKER="${MAM_LOOP_MARKER:-$MAM_REAL_ROOT/.mam/loop-guard-active}"
_mam_release_guard() {
  mam_release_loop_lock "$MAM_LOOP_MARKER" || true
  # B-13: 스냅샷은 우리가 만들었을 때만 지운다 (외부 주입 값은 건드리지 않음)
  if [ -n "${MAM_LOOP_FREEZE_DIR:-}" ] && [ "${MAM_LOOP_FREEZE_OWNED:-0}" = "1" ]; then
    case "$MAM_LOOP_FREEZE_DIR" in
      */mam-loop-freeze.*) rm -rf "$MAM_LOOP_FREEZE_DIR" ;;
      *) : ;;
    esac
  fi
}

# Runs the delegate-job wrapper from the frozen skills tree (REPO_ROOT).
# Stage 2 creates a single snapshot at loop initialization outside the skill tree (B-13),
# keeping the running loop immune across turns without per-call copies or tree pollution (B-6).
delegate_job_safe() {
  local wrapper_script="$REPO_ROOT/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job"
  local rc=0
  bash "$wrapper_script" "$@" || rc=$?
  if [ "$rc" -ne 0 ]; then
    log_error "delegate_job_safe failed (exit $rc): $wrapper_script"
    log_error "  if this loop edits framework skills in place, check that file's syntax:"
    log_error "  bash -n \"$wrapper_script\""
  fi
  return $rc
}

log_info() {
  echo -e "\033[1;34m[*]\033[0m $1"
}

log_success() {
  echo -e "\033[1;32m[✓]\033[0m $1"
}

log_warn() {
  echo -e "\033[1;33m[!]\033[0m $1"
}

log_error() {
  echo -e "\033[1;31m[✗]\033[0m $1"
}

_mam_acquire_rc=0
mam_acquire_loop_lock "$MAM_LOOP_MARKER" || _mam_acquire_rc=$?
case "$_mam_acquire_rc" in
  0) ;;
  3)
    _holder_pid=$(_mam_lock_field "$MAM_LOOP_MARKER" "pid" 2>/dev/null || echo "unknown")
    log_error "Another multi-agent-mux-loop is already running in this workspace."
    log_error "  holder PID: $_holder_pid"
    log_error "  refusing to start a second loop (SQLite/YAML would race)."
    exit 1
    ;;
  *)
    log_error "Cannot determine whether another loop is running (ps unavailable?)."
    log_error "  refusing to start rather than risk two loops writing the registry."
    exit 1
    ;;
esac
trap _mam_release_guard EXIT INT TERM HUP
rm -f "$MAM_REAL_ROOT/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job".*.tmp 2>/dev/null || true

# --all-reviewer silently takes precedence over an explicit --reviewer list;
# warn so the discarded list isn't mistaken for having been honored (P2-1).
if [ "$ALL_REVIEWERS" = true ] && [ -n "$REVIEWER_LIST" ]; then
  log_warn "--all-reviewer takes precedence; ignoring --reviewer list ('$REVIEWER_LIST')."
fi

if [ "$PLAN_TALK_TURNS" -gt 0 ] && [ "$PLAN_MODE" = false ]; then
  log_warn "--plan-talk was specified but --plan mode is not enabled. Discussion turns will be ignored."
fi

# Verdict must occupy the report's last non-blank line — a standalone token
# quoted mid-report (e.g. as a formatting example) never matches (P0-2).
has_verdict() {
  local file="$1" verdict="$2"
  local last_line pattern
  last_line=$(grep -v '^[[:space:]]*$' "$file" 2>/dev/null | tail -n 1)
  pattern="^\[VERDICT: ${verdict}\][[:space:]]*\r?\$"
  [[ "$last_line" =~ $pattern ]]
}

# Detect a standalone '[TAG]' line anywhere in a report FILE.
#
# Always read the file, never a concatenated aggregate: FEEDBACK_AGGREGATE
# joins reports with a literal backslash-n (bash `echo` without -e leaves it
# as text), so a tag on a report's FIRST line ends up sharing a physical line
# with the '--- Reviewer (x) Feedback ---' banner and the '^' anchor misses it.
has_tag_line() {
  local file="$1" tag="$2"
  [ -f "$file" ] || return 1
  grep -qE "^\[${tag}\][[:space:]]*\r?\$" "$file"
}

# Emit the payload of every standalone '[KEY: value]' line in a report file.
extract_tag_values() {
  local file="$1" key="$2"
  [ -f "$file" ] || return 0
  sed -n -E "s/^\[${key}:[[:space:]]*([^]]*)\][[:space:]]*\r?$/\1/p" "$file" \
    | sed -E 's/[[:space:]]+$//' | grep -v '^$' || true
}

# Locate a job's final report (transient .mam job tree, agent-name subdir).
find_report() {
  find ".mam/jobs/$1" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true
}

# Helper: Blocking wait for a delegate job's completion or error state (with safety timeout)
wait_for_job() {
  local job_id="$1"
  local check_interval=3
  local max_wait="${2:-3900}"
  local deadline
  deadline=$((SECONDS + max_wait))
  
  if [ "$VERBOSE" = true ]; then
    log_info "Monitoring job '$job_id' for status changes (timeout: ${max_wait}s)..."
  fi

  while [ "$SECONDS" -lt "$deadline" ]; do
    local status
    status=$(python3 -c "
import json, os
try:
    with open('.mam/jobs/$job_id.json') as f:
        print(json.load(f).get('status', 'unknown'))
except Exception:
    print('unknown')
" 2>/dev/null || echo "unknown")

    if [ "$status" = "completed" ]; then
      if [ "$VERBOSE" = true ]; then
        log_success "Job '$job_id' completed successfully."
      fi
      return 0
    elif [ "$status" = "error" ]; then
      log_error "Job '$job_id' finished with errors."
      return 1
    elif [ "$status" = "cancelled" ]; then
      log_error "Job '$job_id' was cancelled."
      return 1
    fi
    sleep "$check_interval"
  done
  log_error "Job '$job_id' timed out after ${max_wait}s."
  return 1
}

# Resolve active reviewers (excluding $TARGET_AGENT) via lib.sh's
# load_state_json — the single source of truth for the merged DB/YAML
# session state, instead of hand-rolling a 4th copy of that lookup (P1-2).
resolve_all_reviewers() {
  TARGET_AGENT="$TARGET_AGENT" MAM_STATE_JSON="$(load_state_json)" python3 -c "
import os, json
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
target_agent = os.environ.get('TARGET_AGENT')
reviewers = [s.get('name') for s in d.get('herdr_sessions', [])
             if 'reviewer' in (s.get('role') or '').lower() and s.get('status') == 'running' and s.get('name') != target_agent]
print(','.join(reviewers))
"
}

# Resolve target agent type (claude, hermes, agy) via load_state_json.
resolve_agent_type() {
  local name="$1"
  NAME="$name" MAM_STATE_JSON="$(load_state_json)" python3 -c "
import os, json
name = os.environ.get('NAME')
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
agent = None
for s in d.get('herdr_sessions', []):
    if s.get('name') == name:
        agent = s.get('agent') or s.get('pane', {}).get('cmd')
        break
if not agent:
    # Exact hyphen-segment match, not a naive substring 'in' check, so a
    # decoy substring inside an unrelated segment can't misclassify.
    segments = name.split('-')
    if 'agy' in segments:
        agent = 'agy'
    elif 'hermes' in segments:
        agent = 'hermes'
    elif 'grok' in segments:
        agent = 'grok'
    elif 'opencode' in segments:
        agent = 'opencode'
    else:
        agent = 'claude'
print(agent)
"
}

# Resolve planner session dynamically via load_state_json.
resolve_planner_session() {
  MAM_STATE_JSON="$(load_state_json)" python3 -c "
import os, json
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
planner = ''
for s in d.get('herdr_sessions', []):
    if 'planner' in (s.get('role') or '').lower() and s.get('status') == 'running':
        planner = s.get('name')
        break
print(planner)
"
}

# Portable Job ID extraction helper (fails-safe, avoids SC1091/grep GNU dependency)
extract_job_id() {
  local output="$1"
  local job_id
  # Portable extraction equivalent to PCRE K
  job_id=$(echo "$output" | grep -o 'registered job: [A-Za-z0-9]*' | awk '{print $3}' || true)
  echo "$job_id"
}

# Main Execution Loop Flow
log_info "Initializing multi-agent-mux-loop controller..."
log_info "Target Agent: $TARGET_AGENT"
log_info "Task Goal: $TASK"

# Validate that Target Agent is registered and running
TARGET_STATUS=$(MAM_STATE_JSON="$(load_state_json)" TARGET="$TARGET_AGENT" python3 -c "
import os, json
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
target = os.environ.get('TARGET')
status = ''
for s in d.get('herdr_sessions', []):
    if s.get('name') == target:
        status = s.get('status')
        break
print(status)
")

if [ -z "$TARGET_STATUS" ]; then
  log_error "Target agent session '$TARGET_AGENT' is not registered in the session registry."
  exit 1
elif [ "$TARGET_STATUS" != "running" ]; then
  log_error "Target agent session '$TARGET_AGENT' is not running (current status: '$TARGET_STATUS'). Please start it first."
  exit 1
fi

if [ -n "${PLANNER_SESSION_OVERRIDE:-}" ]; then
  # Explicit --planner skips auto-resolve. Pre-freeze already required --plan.
  PLANNER_SESSION="$PLANNER_SESSION_OVERRIDE"
  PLANNER_STATUS=$(MAM_STATE_JSON="$(load_state_json)" TARGET="$PLANNER_SESSION" python3 -c "
import os, json
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
target = os.environ.get('TARGET')
status = ''
for s in d.get('herdr_sessions', []):
    if s.get('name') == target:
        status = s.get('status')
        break
print(status)
")
  if [ -z "$PLANNER_STATUS" ]; then
    log_error "Specified planner session '$PLANNER_SESSION' is not registered in the session registry."
    exit 1
  elif [ "$PLANNER_STATUS" != "running" ]; then
    log_error "Specified planner session '$PLANNER_SESSION' is not running (current status: '$PLANNER_STATUS')."
    exit 1
  fi
else
  PLANNER_SESSION=$(resolve_planner_session)
  if [ "$PLAN_MODE" = true ] && [ -z "$PLANNER_SESSION" ]; then
    log_error "Planner mode enabled (--plan) but no running session with a 'planner' role was found."
    exit 1
  fi
fi
log_info "Resolved Planner session: $PLANNER_SESSION"

CURRENT_PLAN=""
CREATED_JOBS=()

# ===========================================================================
# PHASE 1: PLANNING & DISCUSSIONS
# ===========================================================================
if [ "$PLAN_MODE" = true ]; then
  log_info "=== Phase 1: Interactive Planning Phase ==="
  
  # Step 1.1: Request initial plan from Planner
  log_info "Requesting initial implementation plan from Planner..."
  PLAN_JOB_OUTPUT=$(delegate_job_safe submit \
    --agent-session "herdr:$PLANNER_SESSION" \
    --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
    --type "direct" \
    --role "Planner" \
    --prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK")
  
  PLAN_JOB_ID=$(extract_job_id "$PLAN_JOB_OUTPUT")
  if [ -z "$PLAN_JOB_ID" ]; then
    log_error "Failed to register planner job. Output:\n$PLAN_JOB_OUTPUT"
    exit 1
  fi
  CREATED_JOBS+=("$PLAN_JOB_ID")
  log_info "Planner Job ID: $PLAN_JOB_ID"
  
  if ! wait_for_job "$PLAN_JOB_ID"; then
    log_error "Planning phase failed during initial plan design."
    exit 1
  fi
  
  # Retrieve plan text safely
  PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
  if [ -z "$PLAN_FILE" ] || [ ! -f "$PLAN_FILE" ]; then
    log_error "Final plan file not found."
    exit 1
  fi
  CURRENT_PLAN=$(cat "$PLAN_FILE")
  
  # Step 1.2: Interactive Creator-Planner Debate
  turn=1
  while [ "$turn" -le "$PLAN_TALK_TURNS" ]; do
    log_info "Discussion Turn $turn/$PLAN_TALK_TURNS: Creator challenging the plan..."
    
    # Creator critique job
    DEBATE_JOB_OUTPUT=$(delegate_job_safe submit \
      --agent-session "herdr:$TARGET_AGENT" \
      --agent "$(resolve_agent_type "$TARGET_AGENT")" \
      --type "direct" \
      --role "Worker" \
      --prompt "Planner가 제시한 다음 계획서를 꼼꼼히 검토하고, 실제 구현 시 마주할 수 있는 맹점이나 제약사항 1가지를 발굴하여 Planner에게 이의를 제기(Challenge)해주세요. 계획서:\n$CURRENT_PLAN")
    
    DEBATE_JOB_ID=$(extract_job_id "$DEBATE_JOB_OUTPUT")
    if [ -z "$DEBATE_JOB_ID" ]; then
      log_error "Failed to register Creator critique job. Output:\n$DEBATE_JOB_OUTPUT"
      exit 1
    fi
    CREATED_JOBS+=("$DEBATE_JOB_ID")
    log_info "Critique Job ID: $DEBATE_JOB_ID"
    
    if ! wait_for_job "$DEBATE_JOB_ID"; then
      log_error "Creator critique step failed."
      exit 1
    fi
    
    CRITIQUE_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
    if [ -z "$CRITIQUE_FILE" ] || [ ! -f "$CRITIQUE_FILE" ]; then
      log_error "Critique file not found."
      exit 1
    fi
    CRITIQUE_TEXT=$(cat "$CRITIQUE_FILE")
    
    log_info "Planner refining plan with Creator's feedback..."
    REFINE_JOB_OUTPUT=$(delegate_job_safe submit \
      --agent-session "herdr:$PLANNER_SESSION" \
      --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
      --type "direct" \
      --role "Planner" \
      --prompt "작업자(Creator)로부터 다음 이의제기 피드백을 받았습니다. 피드백을 반영하여 계획서를 정교하게 업데이트(Refine)하여 다시 출력해주세요. 피드백:\n$CRITIQUE_TEXT\n기존 계획서:\n$CURRENT_PLAN")
    
    REFINE_JOB_ID=$(extract_job_id "$REFINE_JOB_OUTPUT")
    if [ -z "$REFINE_JOB_ID" ]; then
      log_error "Failed to register plan refinement job. Output:\n$REFINE_JOB_OUTPUT"
      exit 1
    fi
    CREATED_JOBS+=("$REFINE_JOB_ID")
    log_info "Refinement Job ID: $REFINE_JOB_ID"
    
    if ! wait_for_job "$REFINE_JOB_ID"; then
      log_error "Planner refinement step failed."
      exit 1
    fi
    
    REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
    if [ -z "$REFINE_FILE" ] || [ ! -f "$REFINE_FILE" ]; then
      log_error "Refinement plan file not found."
      exit 1
    fi
    CURRENT_PLAN=$(cat "$REFINE_FILE")
    
    turn=$((turn + 1))
  done
  log_success "Interactive planning completed. Plan finalized."
else
  log_info "=== Phase 1: Self-Planning Mode (Direct Execution) ==="
  EXISTING_PLAN_FILE=""
  if [ -n "$PLANNER_SESSION" ]; then
    # Promoted plans are saved as plan-<job-id>.md by the Phase 3 promotion
    # step below, not report-final.md; pick the most recently promoted one.
    EXISTING_PLAN_FILE=$(ls -t ".agents/reports/$PLANNER_SESSION"/plan-*.md 2>/dev/null | head -n 1 || true)
  fi
  if [ -n "$EXISTING_PLAN_FILE" ] && [ -f "$EXISTING_PLAN_FILE" ]; then
    log_info "Found existing promoted plan at '$EXISTING_PLAN_FILE'. Loading plan..."
    CURRENT_PLAN=$(cat "$EXISTING_PLAN_FILE")
  fi
fi

# ===========================================================================
# PHASE 2: IMPLEMENTATION (CREATION)
# ===========================================================================
log_info "=== Phase 2: Code Implementation ==="

# Fix the pre-implementation commit as the diff baseline so review diffs stay
# cumulative and non-empty even after the Creator commits per DoD (P0-1).
BASE_COMMIT=$(cd -P "$MAM_REAL_ROOT" 2>/dev/null && git rev-parse HEAD 2>/dev/null || echo "")

EXECUTION_PROMPT="계획서가 존재하지 않으므로, 작업자(Creator)의 판단하에 스스로 구현 계획 및 설계를 수립한 뒤, 이를 바탕으로 코드를 구현하고 다음 작업 목표를 완성해주세요. 작업 목표: $TASK"
if [ -n "$CURRENT_PLAN" ]; then
  EXECUTION_PROMPT="이미 수립된 다음 계획서에 입각하여 작업자의 판단하에 코드를 구현하고 작업 목표를 완성해주세요. 계획서:\n$CURRENT_PLAN\n\n작업 목표: $TASK"
fi

EXEC_JOB_OUTPUT=$(delegate_job_safe submit \
  --agent-session "herdr:$TARGET_AGENT" \
  --agent "$(resolve_agent_type "$TARGET_AGENT")" \
  --type "direct" \
  --role "Worker" \
  --prompt "$EXECUTION_PROMPT")

EXEC_JOB_ID=$(extract_job_id "$EXEC_JOB_OUTPUT")
if [ -z "$EXEC_JOB_ID" ]; then
  log_error "Failed to register Creator execution job. Output:\n$EXEC_JOB_OUTPUT"
  exit 1
fi
CREATED_JOBS+=("$EXEC_JOB_ID")
log_info "Creator Job ID: $EXEC_JOB_ID"

if ! wait_for_job "$EXEC_JOB_ID"; then
  log_error "Creator execution failed."
  exit 1
fi
log_success "Initial implementation finished."

# ===========================================================================
# PHASE 3: VERIFICATION LOOP (PEER REVIEW)
# ===========================================================================
log_info "=== Phase 3: Verification & Corrective Review Loop ==="

# Resolve reviewer array
REVIEWERS=()
if [ "$ALL_REVIEWERS" = true ]; then
  # Parse list safely using command substitution + fallback
  RESOLVED_REVS=$(resolve_all_reviewers)
  if [ -n "$RESOLVED_REVS" ]; then
    IFS=' ,' read -r -a REVIEWERS <<< "$RESOLVED_REVS"
  fi
  if [ "${#REVIEWERS[@]}" -eq 0 ]; then
    log_error "--all-reviewer was specified, but no running reviewer sessions were found."
    exit 1
  fi
elif [ -n "$REVIEWER_LIST" ]; then
  # Strip whitespaces first to prevent empty array elements from trailing spaces
  CLEAN_REVS=$(echo "$REVIEWER_LIST" | tr -d '[:space:]')
  IFS=',' read -r -a REVIEWERS <<< "$CLEAN_REVS"
fi

loop_count=1
while [ "$loop_count" -le "$MAX_LOOP" ]; do
  log_info "Review Loop Iteration $loop_count/$MAX_LOOP..."
  
  # O-1: the per-iteration budget resets each pass, because each pass produces
  # NEW findings that may be legitimately rebuttable. The run-wide total is
  # capped separately by REBUT_TOTAL_BUDGET so the reset cannot be mistaken for
  # an unbounded channel (see MULTI_AGENT_RULES.md §3.1 rule 3).
  REBUT_BUDGET="$MAX_REBUT"
  
  if [ "${#REVIEWERS[@]}" -eq 0 ]; then
    log_warn "No reviewers specified. Conducting Creator Self-Review..."
    SELF_REV_OUTPUT=$(delegate_job_safe submit \
      --agent-session "herdr:$TARGET_AGENT" \
      --agent "$(resolve_agent_type "$TARGET_AGENT")" \
      --type "direct" \
      --role "Reviewer" \
      --prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요. 리뷰 리포트 마지막에 단독 행으로 반드시 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 명시해주세요.")
    
    SELF_REV_ID=$(extract_job_id "$SELF_REV_OUTPUT")
    if [ -z "$SELF_REV_ID" ]; then
      log_error "Failed to register Self-Review job."
      exit 1
    fi
    CREATED_JOBS+=("$SELF_REV_ID")
    wait_for_job "$SELF_REV_ID"
    
    REPORT_FILE=$(find ".mam/jobs/$SELF_REV_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
    if [ -n "$REPORT_FILE" ] && [ -f "$REPORT_FILE" ] && has_verdict "$REPORT_FILE" "PASS" && ! has_verdict "$REPORT_FILE" "NOT PASS"; then
      log_success "Self-Review PASS."
      break
    else
      log_warn "Self-Review NOT PASS."
      if [ "$loop_count" -eq "$MAX_LOOP" ]; then
        log_error "Reached max loop count. Self-review loop aborted with failures."
        exit 1
      fi
    fi
  else
    log_info "Active reviewers: ${REVIEWERS[*]}"
    
    CHANGES_DIFF=$(mam_collect_changes_diff "$MAM_REAL_ROOT" "$BASE_COMMIT") || {
      log_error "Could not determine the change set; refusing to request a review on no evidence."
      log_error "$CHANGES_DIFF"
      exit 1
    }

    # We use space-separated lists or simple loops to bypass bash-4 associative array requirement (M-7 macOS compatibility)
    declare -a JOB_IDS=()
    declare -a JOB_REVS=()
    # Objecting reviewers and their report paths, kept in parallel arrays so the
    # rebuttal round can address each objection individually (O-1).
    declare -a FAIL_REVS=()
    declare -a FAIL_REPORTS=()
    
    for rev in "${REVIEWERS[@]}"; do
      log_info "Requesting code review from Reviewer '$rev'..."
      
      REV_OUTPUT=$(delegate_job_safe submit \
        --agent-session "herdr:$rev" \
        --agent "$(resolve_agent_type "$rev")" \
        --type "direct" \
        --role "Reviewer" \
        --prompt "다음 구현 사항(작업 목표: $TASK) 및 누적 변경분(git diff)에 대해 린트, 동작성, 유실 등의 관점에서 교차 코드 리뷰를 수행해주세요. 확인 후 최종 Verdict로 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 리뷰 리포트 마지막에 단독 행으로 명시적으로 작성해주세요. 만약 단순 버그 수정으로는 부족하고 설계 변경/재작업 수준의 재계획이 필요하다고 판단되면, 리포트 아무 곳에나 단독 행으로 '[ESCALATE: PLANNER]' 태그도 함께 남겨주세요. 변경분:\n$CHANGES_DIFF")
      
      REV_JOB_ID=$(extract_job_id "$REV_OUTPUT")
      if [ -z "$REV_JOB_ID" ]; then
        log_error "Failed to register review job for '$rev'."
        exit 1
      fi
      CREATED_JOBS+=("$REV_JOB_ID")
      JOB_IDS+=("$REV_JOB_ID")
      JOB_REVS+=("$rev")
    done
    
    # Wait for all reviews
    all_passed=true
    FEEDBACK_AGGREGATE=""
    
    for idx in "${!JOB_IDS[@]}"; do
      job_id="${JOB_IDS[$idx]}"
      rev="${JOB_REVS[$idx]}"
      
      if ! wait_for_job "$job_id"; then
        log_warn "Reviewer '$rev' job crashed."
        all_passed=false
        continue
      fi
      
      # Parse verdict from report file (fails-safe, M-2 anchored checks)
      REPORT_FILE=$(find ".mam/jobs/$job_id" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
      if [ -z "$REPORT_FILE" ] || [ ! -f "$REPORT_FILE" ]; then
        log_warn "Reviewer '$rev' report not found. Counting as NOT PASS."
        all_passed=false
        continue
      fi
      
      REPORT_CONTENT=$(cat "$REPORT_FILE" 2>/dev/null || echo "")
      
      # Precedence rules: NOT PASS wins over PASS. Absence of verdict tags is treated as NOT PASS (fail-closed)
      if has_verdict "$REPORT_FILE" "NOT PASS" || ! has_verdict "$REPORT_FILE" "PASS"; then
        log_warn "Reviewer '$rev': NOT PASS"
        all_passed=false
        FAIL_REVS+=("$rev")
        FAIL_REPORTS+=("$REPORT_FILE")
        FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT"
      else
        log_success "Reviewer '$rev': PASS"
      fi
    done
    
    if [ "$all_passed" = true ]; then
      log_success "All reviewers issued [VERDICT: PASS]. Loop completed successfully."
      break
    else
      if [ "$loop_count" -eq "$MAX_LOOP" ]; then
        log_error "Reached max loop count ($MAX_LOOP). Review loop aborted with failures."
        exit 1
      fi
      
      # Scan the report FILES, not FEEDBACK_AGGREGATE: the aggregate's literal
      # '\n' separators break '^' anchoring for a tag on a report's first line.
      # bash 3.2 (macOS stock) aborts on "${arr[@]}" when arr is empty under
      # `set -u`. FAIL_REPORTS IS empty on the crashed-job / missing-report
      # paths above, which reach here via all_passed=false without appending.
      COMPLEX_FIX=false
      for _fr in ${FAIL_REPORTS[@]+"${FAIL_REPORTS[@]}"}; do
        if has_tag_line "$_fr" "ESCALATE: PLANNER"; then
          COMPLEX_FIX=true
          break
        fi
      done
      
      if [ "$PLAN_MODE" = true ] && [ "$COMPLEX_FIX" = true ]; then
        log_warn "Feedback involves complex code modifications. Diverting to Planner to revise plan..."
        
        REFINE_PLAN_OUTPUT=$(delegate_job_safe submit \
          --agent-session "herdr:$PLANNER_SESSION" \
          --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
          --type "direct" \
          --role "Planner" \
          --prompt "리뷰어들로부터 다음과 같이 정교한 코드 수정 피드백이 도착했습니다. 해당 피드백을 수렴하여 구현 계획서(Plan)를 갱신(Refine)하여 다시 작성해주세요. 피드백:\n$FEEDBACK_AGGREGATE\n기존 계획서:\n$CURRENT_PLAN")
        
        REFINE_PLAN_ID=$(extract_job_id "$REFINE_PLAN_OUTPUT")
        if [ -z "$REFINE_PLAN_ID" ]; then
          log_error "Failed to register plan refinement job."
          exit 1
        fi
        CREATED_JOBS+=("$REFINE_PLAN_ID")
        wait_for_job "$REFINE_PLAN_ID"
        
        REFINE_PLAN_FILE=$(find ".mam/jobs/$REFINE_PLAN_ID" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
        if [ -z "$REFINE_PLAN_FILE" ] || [ ! -f "$REFINE_PLAN_FILE" ]; then
          log_error "Refined plan file not found."
          exit 1
        fi
        CURRENT_PLAN=$(cat "$REFINE_PLAN_FILE")
        
        CORRECTION_PROMPT="갱신된 다음 계획서에 입각하여 지적된 오류들을 수정하고 코드를 다시 구현해주세요. 계획서:\n$CURRENT_PLAN\n피드백 상세:\n$FEEDBACK_AGGREGATE"
      else
        log_info "Applying straight bugfixes based on reviewer feedback..."
        CORRECTION_PROMPT="리뷰어들이 지적한 다음 피드백에 입각하여 코드를 수정해주세요. 피드백:\n$FEEDBACK_AGGREGATE"
      fi
      
      if [ "$MAX_REBUT" -gt 0 ]; then
        CORRECTION_PROMPT="$CORRECTION_PROMPT\n\n[이의제기 채널] 위 피드백 중 타당하지 않다고 판단되는 항목이 있다면, 그 항목은 구현하지 말고 반론을 제기하십시오. 반론 시 리포트에 단독 행으로 '[REBUT: <리뷰어_세션명>]' 태그를 남기고, 해당 리뷰어의 지적 중 어느 항목을 왜 거부하는지 근거를 함께 서술하십시오. 타당한 지적은 정상적으로 반영하십시오. 반론할 항목이 없으면 태그를 남기지 마십시오. 대상 리뷰어 세션명: ${FAIL_REVS[*]-(없음)}"
      fi
      
      # Creator execution corrective job
      CORRECT_JOB_OUTPUT=$(delegate_job_safe submit \
        --agent-session "herdr:$TARGET_AGENT" \
        --agent "$(resolve_agent_type "$TARGET_AGENT")" \
        --type "direct" \
        --role "Worker" \
        --prompt "$CORRECTION_PROMPT")
      
      CORRECT_JOB_ID=$(extract_job_id "$CORRECT_JOB_OUTPUT")
      if [ -z "$CORRECT_JOB_ID" ]; then
        log_error "Failed to register Creator correction job."
        exit 1
      fi
      CREATED_JOBS+=("$CORRECT_JOB_ID")
      wait_for_job "$CORRECT_JOB_ID"

      # ---------------------------------------------------------------------
      # O-1: REBUTTAL & ADJUDICATION ROUND
      # ---------------------------------------------------------------------
      # The Creator's correction report was previously produced and discarded.
      # Read it: standalone '[REBUT: <reviewer>]' lines open an adjudication
      # round with exactly those reviewers.
      #
      # Invariant: a rebuttal NEVER substitutes for a PASS. A sustained
      # rebuttal only withdraws an objection; the next iteration still runs a
      # full review pass, so the loop can still only exit 0 on unanimous
      # [VERDICT: PASS].
      if [ "$MAX_REBUT" -gt 0 ] && [ "$REBUT_BUDGET" -gt 0 ] && [ "$REBUT_TOTAL_BUDGET" -gt 0 ]; then
        CORRECT_REPORT=$(find_report "$CORRECT_JOB_ID")
        REBUT_TARGETS=()
        if [ -n "$CORRECT_REPORT" ]; then
          while IFS= read -r _t; do
            [ -n "$_t" ] || continue
            # Only honour rebuttals aimed at a reviewer that actually objected,
            # and de-duplicate: a Creator may file one [REBUT:] per rejected
            # finding, all naming the same reviewer — that is ONE adjudication.
            for _fv in ${FAIL_REVS[@]+"${FAIL_REVS[@]}"}; do
              [ "$_t" = "$_fv" ] || continue
              _dup=0
              for _e in ${REBUT_TARGETS[@]+"${REBUT_TARGETS[@]}"}; do
                if [ "$_e" = "$_t" ]; then _dup=1; break; fi
              done
              [ "$_dup" -eq 0 ] && REBUT_TARGETS+=("$_t")
              break
            done
          done <<< "$(extract_tag_values "$CORRECT_REPORT" "REBUT")"
        fi

        if [ "${#REBUT_TARGETS[@]}" -gt 0 ]; then
          REBUT_BUDGET=$((REBUT_BUDGET - 1))
          REBUT_TOTAL_BUDGET=$((REBUT_TOTAL_BUDGET - 1))
          REBUTTAL_TEXT=$(cat "$CORRECT_REPORT" 2>/dev/null || echo "")
          log_warn "Creator filed a rebuttal against: ${REBUT_TARGETS[*]} (iteration budget left: $REBUT_BUDGET, run total left: $REBUT_TOTAL_BUDGET)"

          OVERRULED_REVS=()
          for rt in "${REBUT_TARGETS[@]}"; do
            log_info "Re-adjudication: returning rebuttal to Reviewer '$rt'..."
            ADJ_OUTPUT=$(delegate_job_safe submit \
              --agent-session "herdr:$rt" \
              --agent "$(resolve_agent_type "$rt")" \
              --type "direct" \
              --role "Reviewer" \
              --prompt "작업자(Creator)가 귀하의 리뷰 지적 중 일부를 타당하지 않다고 판단하여 반론을 제기했습니다. 반론을 검토하고 재심(re-adjudication)하십시오. 반론이 타당하여 귀하의 지적을 철회한다면 리포트 마지막에 단독 행으로 '[ADJUDICATION: SUSTAINED]' 를, 반론이 타당하지 않아 지적을 유지한다면 '[ADJUDICATION: OVERRULED]' 를 명시하십시오. 반론 내용:\n$REBUTTAL_TEXT")

            ADJ_JOB_ID=$(extract_job_id "$ADJ_OUTPUT")
            if [ -z "$ADJ_JOB_ID" ]; then
              log_error "Failed to register re-adjudication job for '$rt'."
              exit 1
            fi
            CREATED_JOBS+=("$ADJ_JOB_ID")
            if ! wait_for_job "$ADJ_JOB_ID"; then
              log_warn "Re-adjudication job for '$rt' failed; objection stands."
              OVERRULED_REVS+=("$rt")
              continue
            fi

            ADJ_REPORT=$(find_report "$ADJ_JOB_ID")
            # Fail-closed: anything that is not an explicit SUSTAINED keeps the
            # reviewer's objection alive.
            if [ -n "$ADJ_REPORT" ] && has_tag_line "$ADJ_REPORT" "ADJUDICATION: SUSTAINED"; then
              log_success "Reviewer '$rt' SUSTAINED the rebuttal; objection withdrawn."
            else
              log_warn "Reviewer '$rt' OVERRULED the rebuttal; objection stands."
              OVERRULED_REVS+=("$rt")
            fi
          done

          # Deadlock: the Creator rebutted and the reviewer held. In --plan mode
          # the Planner arbitrates; otherwise the reviewer prevails (fail-closed,
          # charter §1: a reviewer's PASS is the completion gate).
          if [ "${#OVERRULED_REVS[@]}" -gt 0 ]; then
            ARBITRATION="REVIEWER"
            if [ "$PLAN_MODE" = true ] && [ -n "$PLANNER_SESSION" ]; then
              log_info "Deadlock on ${OVERRULED_REVS[*]}; escalating to Planner for arbitration..."
              ARB_OUTPUT=$(delegate_job_safe submit \
                --agent-session "herdr:$PLANNER_SESSION" \
                --agent "$(resolve_agent_type "$PLANNER_SESSION")" \
                --type "direct" \
                --role "Planner" \
                --prompt "작업자(Creator)와 리뷰어의 의견이 충돌하여 교착 상태입니다. 계획서 관점에서 어느 쪽이 타당한지 재정(arbitration)하십시오. 리포트 마지막에 단독 행으로 '[ARBITRATION: CREATOR]' 또는 '[ARBITRATION: REVIEWER]' 를 명시하십시오. 리뷰어 지적:\n$FEEDBACK_AGGREGATE\n작업자 반론:\n$REBUTTAL_TEXT\n기존 계획서:\n$CURRENT_PLAN")

              ARB_JOB_ID=$(extract_job_id "$ARB_OUTPUT")
              if [ -z "$ARB_JOB_ID" ]; then
                log_error "Failed to register Planner arbitration job."
                exit 1
              fi
              CREATED_JOBS+=("$ARB_JOB_ID")
              if wait_for_job "$ARB_JOB_ID"; then
                ARB_REPORT=$(find_report "$ARB_JOB_ID")
                if [ -n "$ARB_REPORT" ] && has_tag_line "$ARB_REPORT" "ARBITRATION: CREATOR"; then
                  ARBITRATION="CREATOR"
                fi
              else
                log_warn "Planner arbitration job failed; defaulting to REVIEWER."
              fi
            fi

            if [ "$ARBITRATION" = "CREATOR" ]; then
              log_success "Planner ruled for the Creator; objections withdrawn for this round."
            else
              log_warn "Ruling stands with the reviewer(s); Creator must comply."
              # Close the channel for the REST OF THIS ITERATION and require
              # compliance. Note this does not persist into the next iteration
              # (which re-reviews and may raise different findings); the
              # run-wide cap REBUT_TOTAL_BUDGET is what bounds the whole run.
              REBUT_BUDGET=0
              COMPLY_OUTPUT=$(delegate_job_safe submit \
                --agent-session "herdr:$TARGET_AGENT" \
                --agent "$(resolve_agent_type "$TARGET_AGENT")" \
                --type "direct" \
                --role "Worker" \
                --prompt "귀하의 반론은 재심 결과 기각되었습니다. 더 이상 반론하지 말고 다음 리뷰 피드백을 그대로 반영하여 코드를 수정하십시오. 피드백:\n$FEEDBACK_AGGREGATE")

              COMPLY_JOB_ID=$(extract_job_id "$COMPLY_OUTPUT")
              if [ -z "$COMPLY_JOB_ID" ]; then
                log_error "Failed to register Creator compliance job."
                exit 1
              fi
              CREATED_JOBS+=("$COMPLY_JOB_ID")
              wait_for_job "$COMPLY_JOB_ID"
            fi
          fi
        fi
      fi
    fi
  fi
  
  loop_count=$((loop_count + 1))
done

# Promote finalized plan and passed reviewer reports to durable location
if [ "$PLAN_MODE" = true ] && [ -n "${CURRENT_PLAN:-}" ] && [ -n "${PLAN_JOB_ID:-}" ]; then
  plan_dest_dir=".agents/reports/$PLANNER_SESSION"
  log_info "Promoting final plan to durable location: $plan_dest_dir"
  mkdir -p "$plan_dest_dir"
  echo "$CURRENT_PLAN" > "$plan_dest_dir/plan-${PLAN_JOB_ID}.md.tmp"
  mv -f "$plan_dest_dir/plan-${PLAN_JOB_ID}.md.tmp" "$plan_dest_dir/plan-${PLAN_JOB_ID}.md"
fi

if [ "${#REVIEWERS[@]}" -gt 0 ]; then
  log_info "Promoting final review reports to durable location..."
  for idx in "${!JOB_IDS[@]}"; do
    job_id="${JOB_IDS[$idx]}"
    rev="${JOB_REVS[$idx]}"
    report_file=$(find ".mam/jobs/$job_id" -maxdepth 2 -name "report-final.md" 2>/dev/null | head -n 1 || true)
    if [ -n "$report_file" ] && [ -f "$report_file" ]; then
      dest_dir=".agents/reports/$rev"
      mkdir -p "$dest_dir"
      cp "$report_file" "$dest_dir/report-${job_id}.md.tmp"
      mv -f "$dest_dir/report-${job_id}.md.tmp" "$dest_dir/report-${job_id}.md"
    fi
  done
fi

# ===========================================================================
# PHASE 4: CLEANUP
# ===========================================================================
if [ "$CLEANUP" = true ]; then
  log_info "Cleaning up temporary job directories created during this loop run..."
  for job in "${CREATED_JOBS[@]}"; do
    if [ -d ".mam/jobs/$job" ]; then
      rm -rf ".mam/jobs/$job"
      rm -f ".mam/jobs/$job.subscriber.out"
      if [ "$VERBOSE" = true ]; then
        log_info "Purged temp assets for job: $job"
      fi
    fi
  done
  log_success "Cleanup complete."
fi

log_success "Mux loop finished with 100% PASS verdicts."
exit 0
