#!/usr/bin/env bash
# ralph — Goal → Full RPI → PR
# "Ralph Wiggum" pattern: each phase gets a fresh context window.
#
# Usage:
#   ralph "Add dark mode support"
#   ralph --skip-premortem "Fix typo in README"
#   ralph --branch feat/dark-mode "Add dark mode"
#   ralph --spec spec.md "Add dark mode"
#   ralph --dry-run "Add dark mode"
#   ralph --resume .agents/scratch/ralph/<slug>.checkpoint
#   ralph --phase-timeout 900 "Big feature"

set -euo pipefail

# ── Phase order (for checkpoint skip logic) ───────────────────────────────────
PHASES=(plan premortem branch crank vibe postmortem pr)

# ── Defaults ──────────────────────────────────────────────────────────────────
SKIP_PRE_MORTEM=false
DRY_RUN=false
BRANCH=""
GOAL=""
RESUME_FILE=""
RESUME_AFTER=""
SPEC_FILE=""
SPEC_CONTENT=""
PHASE_TIMEOUT=600
CODEX_BIN="${CODEX_BIN:-codex}"

# ── Parse args ────────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
  case "$1" in
    --skip-premortem) SKIP_PRE_MORTEM=true; shift ;;
    --branch)          BRANCH="$2"; shift 2 ;;
    --dry-run)         DRY_RUN=true; shift ;;
    --resume)          RESUME_FILE="$2"; shift 2 ;;
    --spec)            SPEC_FILE="$2"; shift 2 ;;
    --max-budget)
      echo "ralph: --max-budget is no longer supported: Codex subscription execution cannot enforce dollar budgets." >&2
      echo "Use --phase-timeout SECONDS for an enforceable wall-clock bound per phase." >&2
      exit 2
      ;;
    --phase-timeout)   PHASE_TIMEOUT="$2"; shift 2 ;;
    --help|-h)
      sed -n '2,13p' "$0"
      exit 0
      ;;
    -*)
      echo "Unknown option: $1" >&2
      exit 1
      ;;
    *)
      GOAL="$1"; shift
      ;;
  esac
done

# ── Resume from checkpoint ────────────────────────────────────────────────────
if [[ -n "$RESUME_FILE" ]]; then
  if [[ ! -f "$RESUME_FILE" ]]; then
    echo "Checkpoint file not found: $RESUME_FILE" >&2
    exit 1
  fi
  # Validate the checkpoint lives in a ralph checkpoint dir and has the
  # expected header. New checkpoints are written under .agents/scratch/ralph/;
  # the legacy .agents/ralph/ path stays resumable so checkpoints written
  # before the scratch-tier move still work (no migration required).
  case "$RESUME_FILE" in
    .agents/scratch/ralph/*.checkpoint|*/.agents/scratch/ralph/*.checkpoint) ;;
    .agents/ralph/*.checkpoint|*/.agents/ralph/*.checkpoint) ;;
    *) echo "Refusing to source checkpoint outside .agents/scratch/ralph/ (or the legacy .agents/ralph/): $RESUME_FILE" >&2; exit 1 ;;
  esac
  if ! head -1 "$RESUME_FILE" | grep -q '^# Ralph checkpoint'; then
    echo "Invalid checkpoint file (missing header): $RESUME_FILE" >&2
    exit 1
  fi
  # shellcheck source=/dev/null
  source "$RESUME_FILE"
  # LAST_PHASE is set by the checkpoint file
  RESUME_AFTER="${LAST_PHASE:-}"
  echo "Resuming after phase: $RESUME_AFTER"
fi

if [[ -z "$GOAL" ]]; then
  echo "Usage: ralph [options] \"<goal>\"" >&2
  exit 1
fi

# ── Load spec ─────────────────────────────────────────────────────────────────
if [[ -n "$SPEC_FILE" ]]; then
  if [[ ! -f "$SPEC_FILE" ]]; then
    echo "Spec file not found: $SPEC_FILE" >&2
    exit 1
  fi
  SPEC_CONTENT=$(cat "$SPEC_FILE")
fi

# ── Derived values ────────────────────────────────────────────────────────────
BRANCH="${BRANCH:-feat/ralph-$(date +%s)}"
BASE_BRANCH="${BASE_BRANCH:-$(git branch --show-current)}"
WORKDIR="${WORKDIR:-$(pwd)}"
SLUG="${SLUG:-$(echo "$GOAL" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g' | head -c 40)}"
RALPH_DIR=".agents/scratch/ralph"
RALPH_LOG="${RALPH_LOG:-$RALPH_DIR/$(date +%Y-%m-%d)-$SLUG.log}"
CHECKPOINT_FILE="$RALPH_DIR/$SLUG.checkpoint"

mkdir -p "$RALPH_DIR"

# ── Helpers ───────────────────────────────────────────────────────────────────
log() { echo "=== $1 ===" | tee -a "$RALPH_LOG"; }
die() { echo "FATAL: $1" | tee -a "$RALPH_LOG" >&2; exit 1; }

should_skip() {
  local phase="$1"
  if [[ -z "$RESUME_AFTER" ]]; then
    return 1  # no resume, don't skip
  fi
  for p in "${PHASES[@]}"; do
    if [[ "$p" == "$phase" ]]; then
      return 0  # haven't passed RESUME_AFTER yet, skip
    fi
    if [[ "$p" == "$RESUME_AFTER" ]]; then
      return 1  # passed RESUME_AFTER, run this and all subsequent
    fi
  done
  return 1
}

save_checkpoint() {
  local last_phase="$1"
  cat > "$CHECKPOINT_FILE" <<CKPT
# Ralph checkpoint — source this to resume
GOAL=$(printf '%q' "$GOAL")
BRANCH=$(printf '%q' "$BRANCH")
BASE_BRANCH=$(printf '%q' "$BASE_BRANCH")
WORKDIR=$(printf '%q' "$WORKDIR")
SLUG=$(printf '%q' "$SLUG")
SKIP_PRE_MORTEM=$(printf '%q' "$SKIP_PRE_MORTEM")
SPEC_FILE=$(printf '%q' "$SPEC_FILE")
RALPH_LOG=$(printf '%q' "$RALPH_LOG")
LAST_PHASE=$(printf '%q' "$last_phase")
CKPT
  echo "Checkpoint saved: $CHECKPOINT_FILE" | tee -a "$RALPH_LOG"
}

spec_context() {
  if [[ -n "$SPEC_CONTENT" ]]; then
    printf '\n\nThe acceptance spec is:\n%s' "$SPEC_CONTENT"
  fi
}

read_verdict() {
  local file="$1"
  if [[ -f "$file" ]]; then
    cat "$file"
  else
    echo "UNKNOWN"
  fi
}

run_phase() {
  local phase_name="$1"
  local prompt="$2"

  if should_skip "$phase_name"; then
    log "Skipping $phase_name (resuming)"
    return 0
  fi

  log "Phase: $phase_name"

  if [[ "$DRY_RUN" == "true" ]]; then
    echo "[dry-run] Would run codex exec with prompt:" | tee -a "$RALPH_LOG"
    echo "  $prompt" | tee -a "$RALPH_LOG"
    echo "" | tee -a "$RALPH_LOG"
    save_checkpoint "$phase_name"
    return 0
  fi

  if ! command -v "$CODEX_BIN" >/dev/null 2>&1; then
    die "Codex runtime not found: $CODEX_BIN"
  fi

  set +e
  timeout "$PHASE_TIMEOUT" "$CODEX_BIN" exec \
    --full-auto \
    -C "$WORKDIR" \
    "$prompt" 2>&1 | tee -a "$RALPH_LOG"
  local exit_code=${PIPESTATUS[0]}
  set -e
  if [[ $exit_code -eq 124 ]]; then
    save_checkpoint "$phase_name"
    die "Phase $phase_name timed out after ${PHASE_TIMEOUT}s (gutter detected). Resume with: ralph --resume $CHECKPOINT_FILE"
  fi
  if [[ $exit_code -ne 0 ]]; then
    save_checkpoint "$phase_name"
    die "Phase $phase_name failed (exit $exit_code). Resume with: ralph --resume $CHECKPOINT_FILE"
  fi

  save_checkpoint "$phase_name"
}

# ── Banner ────────────────────────────────────────────────────────────────────
echo "" | tee -a "$RALPH_LOG"
log "Ralph Loop starting"
echo "  Goal:       $GOAL" | tee -a "$RALPH_LOG"
echo "  Branch:     $BRANCH" | tee -a "$RALPH_LOG"
echo "  Base:       $BASE_BRANCH" | tee -a "$RALPH_LOG"
echo "  Skip PM:    $SKIP_PRE_MORTEM" | tee -a "$RALPH_LOG"
echo "  Dry run:    $DRY_RUN" | tee -a "$RALPH_LOG"
echo "  Spec:       ${SPEC_FILE:-(none)}" | tee -a "$RALPH_LOG"
echo "  Timeout:    ${PHASE_TIMEOUT}s" | tee -a "$RALPH_LOG"
echo "  Resume:     ${RESUME_AFTER:-(fresh)}" | tee -a "$RALPH_LOG"
echo "  Log:        $RALPH_LOG" | tee -a "$RALPH_LOG"
echo "" | tee -a "$RALPH_LOG"

# ── Phase 1: Plan ─────────────────────────────────────────────────────────────
run_phase "plan" \
  "You are working in $WORKDIR. The goal is: $GOAL$(spec_context)

Run /plan to decompose this goal into trackable issues.
Create a plan document under .agents/plans/.
When done, write the path to the plan file into $RALPH_DIR/plan-path.txt (just the path, nothing else)."

PLAN_PATH="$RALPH_DIR/plan-path.txt"

# ── Phase 2: Pre-mortem (optional) ────────────────────────────────────────────
if [[ "$SKIP_PRE_MORTEM" == "true" ]]; then
  if ! should_skip "premortem"; then
    log "Skipping premortem (--skip-premortem)"
    echo "SKIP" > "$RALPH_DIR/premortem-verdict.txt"
    save_checkpoint "premortem"
  fi
else
  PLAN_FILE=""
  if [[ -f "$PLAN_PATH" ]]; then
    PLAN_FILE=$(cat "$PLAN_PATH")
  fi

  run_phase "premortem" \
    "You are working in $WORKDIR.
Run /premortem on the plan${PLAN_FILE:+ at $PLAN_FILE}.$(spec_context)
After the council finishes, write exactly one word — PASS, WARN, or FAIL — into $RALPH_DIR/premortem-verdict.txt."

  if ! should_skip "premortem"; then
    PRE_MORTEM=$(read_verdict "$RALPH_DIR/premortem-verdict.txt")
    if [[ "$PRE_MORTEM" == "FAIL" ]]; then
      die "Pre-mortem verdict: FAIL — aborting. See $RALPH_LOG for details."
    fi
    log "Pre-mortem verdict: $PRE_MORTEM"
  fi
fi

# ── Create branch ─────────────────────────────────────────────────────────────
if ! should_skip "branch"; then
  if [[ "$DRY_RUN" != "true" ]]; then
    log "Creating branch: $BRANCH"
    git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
  fi
  save_checkpoint "branch"
fi

# ── Phase 3: Crank ────────────────────────────────────────────────────────────
PLAN_FILE=""
if [[ -f "$PLAN_PATH" ]]; then
  PLAN_FILE=$(cat "$PLAN_PATH")
fi

run_phase "crank" \
  "You are working in $WORKDIR on branch $BRANCH. The goal is: $GOAL.
${PLAN_FILE:+The plan is at $PLAN_FILE.}$(spec_context)
Run /crank to implement all planned work. Commit all changes when done."

# ── Phase 4: Vibe ─────────────────────────────────────────────────────────────
run_phase "vibe" \
  "You are working in $WORKDIR on branch $BRANCH.
Run /vibe to validate the implementation.$(spec_context)
After the council finishes, write exactly one word — PASS, WARN, or FAIL — into $RALPH_DIR/vibe-verdict.txt."

if ! should_skip "vibe"; then
  VIBE=$(read_verdict "$RALPH_DIR/vibe-verdict.txt")
  if [[ "$VIBE" == "FAIL" ]]; then
    die "Vibe verdict: FAIL — aborting before PR. See $RALPH_LOG for details."
  fi
  if [[ "$VIBE" == "WARN" ]]; then
    log "Vibe verdict: WARN — continuing with warnings"
  fi
fi

# ── Phase 5: Post-mortem ──────────────────────────────────────────────────────
run_phase "postmortem" \
  "You are working in $WORKDIR on branch $BRANCH.
Run /postmortem to wrap up. Extract learnings.
Write exactly one word — PASS, WARN, or FAIL — into $RALPH_DIR/postmortem-verdict.txt."

# ── Phase 6: PR ───────────────────────────────────────────────────────────────
if should_skip "pr"; then
  log "Skipping PR (resuming)"
else
  log "Creating PR"

  if [[ "$DRY_RUN" == "true" ]]; then
    echo "[dry-run] Would create PR with title: $(echo "$GOAL" | head -c 70)"
    echo "[dry-run] Done."
    exit 0
  fi

  # Push branch
  git push -u origin "$BRANCH"

  # Build PR body
  PLAN_FILE=""
  if [[ -f "$PLAN_PATH" ]]; then
    PLAN_FILE=$(cat "$PLAN_PATH")
  fi

  PR_BODY="## Goal
$GOAL

## Council Verdicts
- Pre-mortem: $(read_verdict "$RALPH_DIR/premortem-verdict.txt")
- Vibe: $(read_verdict "$RALPH_DIR/vibe-verdict.txt")
- Post-mortem: $(read_verdict "$RALPH_DIR/postmortem-verdict.txt")
"

  if [[ -n "$SPEC_CONTENT" ]]; then
    PR_BODY+="
## Spec
$SPEC_CONTENT
"
  fi

  PR_BODY+="
## Plan
$(cat "$PLAN_FILE" 2>/dev/null || echo "See .agents/plans/")

## Reports
See \`.agents/council/\` for full council reports."

  PR_URL=$(gh pr create \
    --base "$BASE_BRANCH" \
    --title "$(echo "$GOAL" | head -c 70)" \
    --body "$PR_BODY")

  save_checkpoint "pr"
  log "Done"
  echo ""
  echo "PR: $PR_URL"
fi

echo "Log: $RALPH_LOG"
