#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'USAGE'
Usage: scripts/ralph-loop [--issue NUMBER] [--afk] [--agent-command CMD] [--prefix-prompt-file FILE] [--print-prompt]

Runs one bounded Ralph implementation loop for a GitHub issue.

Options:
  --issue NUMBER       GitHub issue number to implement.
  --afk                Pick only issues labeled both ready-for-agent and afk.
  --agent-command CMD  Command that receives the Ralph prompt on stdin.
                       Defaults to RALPH_AGENT_COMMAND when set.
  --prefix-prompt-file FILE
                       Extra AgentRail instructions to include in the Ralph prompt.
  --print-prompt       Print the generated prompt instead of running an agent.
  -h, --help           Show this help.

Examples:
  scripts/ralph-loop --issue 123 --print-prompt
  RALPH_AGENT_COMMAND='codex exec --sandbox danger-full-access -' scripts/ralph-loop --issue 123
  scripts/ralph-loop --afk --agent-command 'codex exec --sandbox danger-full-access -'
USAGE
}

issue_number=""
afk=0
print_prompt=0
agent_command="${RALPH_AGENT_COMMAND:-}"
prefix_prompt_file=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --issue)
      issue_number="${2:-}"
      [[ -n "$issue_number" ]] || { echo "--issue requires a number" >&2; exit 2; }
      shift 2
      ;;
    --afk)
      afk=1
      shift
      ;;
    --agent-command)
      agent_command="${2:-}"
      [[ -n "$agent_command" ]] || { echo "--agent-command requires a command" >&2; exit 2; }
      shift 2
      ;;
    --prefix-prompt-file)
      prefix_prompt_file="${2:-}"
      [[ -n "$prefix_prompt_file" && "$prefix_prompt_file" != --* ]] || { echo "--prefix-prompt-file requires a file" >&2; exit 2; }
      shift 2
      ;;
    --print-prompt)
      print_prompt=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unknown option: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
done

repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
cd "$repo_root"

context_file="CONTEXT.md"
ralph_docs_file="docs/agents/ralph-loop.md"
memory_command="scripts/memory"

if [[ ! -f "$context_file" && -f "templates/CONTEXT.md" ]]; then
  context_file="templates/CONTEXT.md"
fi

if [[ ! -f "$ralph_docs_file" && -f "templates/docs/agents/ralph-loop.md" ]]; then
  ralph_docs_file="templates/docs/agents/ralph-loop.md"
fi

if [[ ! -x "$memory_command" && -x "templates/scripts/memory" ]]; then
  memory_command="templates/scripts/memory"
fi

if [[ ! -f "$context_file" ]]; then
  echo "Missing CONTEXT.md. Run the workflow installer from the project root first, or run from the AgentRail source repo with templates/CONTEXT.md present." >&2
  exit 1
fi

if [[ ! -f "$ralph_docs_file" ]]; then
  echo "Missing docs/agents/ralph-loop.md. Run the workflow installer first, or run from the AgentRail source repo with templates/docs/agents/ralph-loop.md present." >&2
  exit 1
fi

if [[ -z "$issue_number" ]]; then
  if ! command -v gh >/dev/null 2>&1; then
    echo "gh CLI is required to auto-pick an issue. Pass --issue NUMBER instead." >&2
    exit 1
  fi

  labels=(--label ready-for-agent)
  if [[ "$afk" -eq 1 ]]; then
    labels+=(--label afk)
  fi

  issue_number="$(gh issue list "${labels[@]}" --state open --limit 1 --json number --jq '.[0].number // empty')"
  if [[ -z "$issue_number" ]]; then
    if [[ "$afk" -eq 1 ]]; then
      echo "No open issue found with labels: ready-for-agent, afk" >&2
    else
      echo "No open issue found with label: ready-for-agent" >&2
    fi
    exit 1
  fi
fi

if ! [[ "$issue_number" =~ ^[0-9]+$ ]]; then
  echo "--issue must be a numeric GitHub issue number" >&2
  exit 2
fi

if command -v gh >/dev/null 2>&1; then
  issue_body="$(gh issue view "$issue_number" --json number,title,body,labels,url --template '{{printf "#%v %s\n%s\n\n" .number .title .url}}{{range .labels}}{{printf "label: %s\n" .name}}{{end}}{{printf "\n%s\n" .body}}' 2>/dev/null || true)"
else
  issue_body=""
fi

if [[ -z "$issue_body" ]]; then
  issue_body="Issue #${issue_number}. GitHub issue details were not loaded, so read the issue manually before implementing."
fi

prefix_prompt=""
if [[ -n "$prefix_prompt_file" ]]; then
  [[ -f "$prefix_prompt_file" ]] || { echo "--prefix-prompt-file does not exist: ${prefix_prompt_file}" >&2; exit 2; }
  prefix_prompt="$(cat "$prefix_prompt_file")"
fi

prompt="$(cat <<PROMPT
You are running one Ralph implementation loop.

Hard limits:
- Handle exactly one issue: #${issue_number}
- Do not continue into unrelated issues.
- Read ${context_file} and ${ralph_docs_file} before editing.
- Run ${memory_command} recall for the issue title and key terms before editing when that command is available.
- Treat project memory as advisory; verify it against current code, docs, issue, PRD, and ADRs.
- Preserve existing user changes.
- Implement the smallest coherent change that satisfies the issue.
- Run relevant verification.
- For UI-visible changes, capture visual evidence.
- Open or update a PR and include linked issue, summary, acceptance criteria coverage, verification, visual evidence, memory updates if any, and risks.
- In the PR body, map every acceptance criterion to implementation evidence and verification evidence.
- Stop when the PR is ready or when blocked.

AgentRail phase instructions:
${prefix_prompt}

Issue:
${issue_body}
PROMPT
)"

if [[ "$print_prompt" -eq 1 || -z "$agent_command" ]]; then
  printf '%s\n' "$prompt"
  if [[ -z "$agent_command" && "$print_prompt" -ne 1 ]]; then
    echo
    echo "No agent command configured. Set RALPH_AGENT_COMMAND or pass --agent-command to run it."
  fi
  exit 0
fi

if [[ "$afk" -eq 1 ]] && command -v gh >/dev/null 2>&1; then
  gh issue edit "$issue_number" --add-label afk-in-progress >/dev/null
fi

portable_timeout() {
  local seconds="$1"
  shift
  if command -v timeout >/dev/null 2>&1; then
    timeout "$seconds" "$@"
    return $?
  fi
  "$@" &
  local pid=$!
  ( sleep "$seconds" && kill -TERM "$pid" 2>/dev/null ) &
  local watcher=$!
  wait "$pid" 2>/dev/null
  local exit_code=$?
  kill "$watcher" 2>/dev/null
  wait "$watcher" 2>/dev/null
  if [[ "$exit_code" -eq 143 ]]; then
    return 124
  fi
  return "$exit_code"
}

sanitized_agent_exec() {
  env \
    -u CLAUDECODE -u CLAUDE_CODE_SESSION_ID -u CLAUDE_CODE_ENTRYPOINT \
    -u CLAUDE_AGENT_SDK_VERSION -u CLAUDE_CODE_EXECPATH -u CLAUDE_EFFORT \
    -u AI_AGENT \
    -u CODEX_SESSION -u CODEX_SANDBOX \
    -u CURSOR_SESSION -u CURSOR_AGENT \
    "$@"
}

ralph_timeout="${RALPH_AGENT_TIMEOUT:-${AGENTRAIL_AGENT_TIMEOUT:-1800}}"
set +e
printf '%s\n' "$prompt" | portable_timeout "$ralph_timeout" sanitized_agent_exec bash -lc "$agent_command"
status=$?
if [[ "$status" -eq 124 ]]; then
  echo "ralph-loop: agent timed out after ${ralph_timeout}s" >&2
fi
set -e

if [[ "$afk" -eq 1 ]] && command -v gh >/dev/null 2>&1; then
  gh issue edit "$issue_number" --remove-label afk-in-progress >/dev/null || true
fi

exit "$status"
