#!/usr/bin/env bash
# tick.sh — one autoresearch cycle, idempotent and safe to schedule.
#
# What it does each tick:
#   1. Capture current measure.sh score.
#   2. If the patient working tree is dirty (uncommitted iter changes from
#      a previous tick or from the user), skip the git pull and skip the
#      claude -p iteration. Just record the measure and exit. This prevents
#      a scheduled tick from running on top of unreviewed changes.
#   3. If clean, fetch origin/xdc-network. If new commits arrived, re-measure
#      and log the delta — this catches regressions when the team merges PRs.
#   4. If the score is below 115 (max), kick off one claude -p iteration
#      against program.md. The iteration may or may not improve the score;
#      the result is logged either way.
#   5. Append a one-line entry to log/scheduled/timeline.log.
#
# Designed to be invoked by launchd / cron / systemd / Cowork scheduler.
# Exits 0 on success (including "skipped due to dirty tree").
# Exits non-zero only on infrastructure failure (missing tools, no patient repo).
#
# Configurable via env:
#   PATIENT       — path to GP5 working tree (default: ../)
#   REFERENCE     — path to v2.6.8 read-only checkout (default: ../../XDPoSChain)
#   CLAUDE_BIN    — path to claude CLI (default: looks up via `command -v`)
#   CLAUDE_MODEL  — model to use (default: claude-opus-4-7)
#   MAX_TURNS     — max conversational turns per iteration (default: 30)
#   PULL          — set to "false" to skip git fetch even when tree is clean
#                   (useful for offline ticks). Default "true".
#   AUTO_ITERATE  — set to "false" to skip claude -p invocation; just measure.
#                   Default "true".

set -uo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PATIENT="${PATIENT:-$(cd "$HERE/.." && pwd)}"
REFERENCE="${REFERENCE:-$(cd "$HERE/../../XDPoSChain" 2>/dev/null && pwd || echo /Users/anilchinchawale/github/XDCNetwork/XDPoSChain)}"
CLAUDE_BIN="${CLAUDE_BIN:-$(command -v claude 2>/dev/null || true)}"
CLAUDE_MODEL="${CLAUDE_MODEL:-claude-opus-4-7}"
MAX_TURNS="${MAX_TURNS:-30}"
PULL="${PULL:-true}"
AUTO_ITERATE="${AUTO_ITERATE:-true}"

STAMP="$(date -u +%Y-%m-%dT%H%M%SZ)"
LOG_DIR="$HERE/log/scheduled/$STAMP"
mkdir -p "$LOG_DIR"
TIMELINE="$HERE/log/scheduled/timeline.log"
mkdir -p "$(dirname "$TIMELINE")"

log() {
  echo "[$(date -u +%H:%M:%S)] $*" | tee -a "$LOG_DIR/run.log"
}

err() {
  log "ERROR: $*"
  echo "$STAMP ERROR $*" >> "$TIMELINE"
  exit 1
}

# ─────────────────────────────────────────────────────────────────────
# Pre-flight
# ─────────────────────────────────────────────────────────────────────

[[ -d "$PATIENT" ]]    || err "PATIENT not found: $PATIENT"
[[ -d "$REFERENCE" ]]  || err "REFERENCE not found: $REFERENCE"
[[ -x "$HERE/measure.sh" ]] || err "measure.sh missing or not executable"

export PATIENT REFERENCE

log "tick start: PATIENT=$PATIENT REFERENCE=$REFERENCE"

# ─────────────────────────────────────────────────────────────────────
# 1. Capture current score
# ─────────────────────────────────────────────────────────────────────

"$HERE/measure.sh" > "$LOG_DIR/before.json" 2> "$LOG_DIR/before.err" || err "measure.sh failed at tick start; check $LOG_DIR/before.err"
SCORE_BEFORE=$(jq -r .score "$LOG_DIR/before.json")
PCT_BEFORE=$(jq -r .percent "$LOG_DIR/before.json")
log "score at tick start: $SCORE_BEFORE (${PCT_BEFORE}%)"

# ─────────────────────────────────────────────────────────────────────
# 2. Dirty-tree check
# ─────────────────────────────────────────────────────────────────────

cd "$PATIENT"
DIRTY=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
if [[ "$DIRTY" -gt 0 ]]; then
  log "patient working tree has $DIRTY uncommitted file(s); skipping pull and iteration"
  log "review and commit changes manually before next tick"
  echo "$STAMP DIRTY score=$SCORE_BEFORE files=$DIRTY (skipped)" >> "$TIMELINE"
  cp "$LOG_DIR/before.json" "$LOG_DIR/after.json"  # no change
  exit 0
fi
log "working tree clean"

# ─────────────────────────────────────────────────────────────────────
# 3. Git fetch (if enabled)
# ─────────────────────────────────────────────────────────────────────

PULLED_NEW=false
if [[ "$PULL" == "true" ]]; then
  HEAD_BEFORE=$(git rev-parse HEAD 2>/dev/null)
  if git fetch origin xdc-network 2>"$LOG_DIR/fetch.log"; then
    HEAD_REMOTE=$(git rev-parse origin/xdc-network 2>/dev/null)
    if [[ "$HEAD_BEFORE" != "$HEAD_REMOTE" ]]; then
      log "new commits on origin/xdc-network: $HEAD_BEFORE -> $HEAD_REMOTE"
      # Update the local xdc-network ref without affecting current branch
      CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "DETACHED")
      if [[ "$CURRENT_BRANCH" == "xdc-network" ]]; then
        # Currently on xdc-network — fast-forward
        if git merge --ff-only origin/xdc-network 2>"$LOG_DIR/merge.log"; then
          log "fast-forwarded local xdc-network to $HEAD_REMOTE"
          PULLED_NEW=true
        else
          log "fast-forward failed (non-FF merge needed); leaving as-is"
        fi
      else
        # On a different branch — update local xdc-network ref directly
        if git update-ref refs/heads/xdc-network "$HEAD_REMOTE" 2>"$LOG_DIR/update-ref.log"; then
          log "updated local xdc-network ref to $HEAD_REMOTE (still on $CURRENT_BRANCH)"
          PULLED_NEW=true
        fi
      fi
    else
      log "no new commits on origin/xdc-network"
    fi
  else
    log "git fetch failed (likely auth or network); continuing offline"
  fi
fi

# ─────────────────────────────────────────────────────────────────────
# 4. Re-measure if pull brought new code
# ─────────────────────────────────────────────────────────────────────

if [[ "$PULLED_NEW" == "true" ]]; then
  "$HERE/measure.sh" > "$LOG_DIR/post-fetch.json" 2>&1 || err "measure failed after fetch"
  SCORE_POST_FETCH=$(jq -r .score "$LOG_DIR/post-fetch.json")
  log "score after fetch: $SCORE_POST_FETCH"
  if [[ "$SCORE_POST_FETCH" -lt "$SCORE_BEFORE" ]]; then
    log "REGRESSION: score dropped from $SCORE_BEFORE to $SCORE_POST_FETCH after fetching new commits"
    echo "$STAMP REGRESSION before=$SCORE_BEFORE after_fetch=$SCORE_POST_FETCH" >> "$TIMELINE"
  fi
  SCORE_BEFORE=$SCORE_POST_FETCH
fi

# ─────────────────────────────────────────────────────────────────────
# 5. Run claude -p if score is below max
# ─────────────────────────────────────────────────────────────────────

MAX=$(jq -r .max "$LOG_DIR/before.json")
if [[ "$AUTO_ITERATE" != "true" ]]; then
  log "AUTO_ITERATE=false; skipping claude -p"
  cp "$LOG_DIR/before.json" "$LOG_DIR/after.json"
  echo "$STAMP NOOP score=$SCORE_BEFORE max=$MAX" >> "$TIMELINE"
  exit 0
fi

if [[ -z "$CLAUDE_BIN" ]]; then
  log "claude CLI not found; skipping iteration. Install from anthropic.com/claude/code"
  cp "$LOG_DIR/before.json" "$LOG_DIR/after.json"
  echo "$STAMP NO_CLAUDE score=$SCORE_BEFORE" >> "$TIMELINE"
  exit 0
fi

if [[ "$SCORE_BEFORE" -ge "$MAX" ]]; then
  log "score is already at max ($SCORE_BEFORE/$MAX); no iteration needed"
  cp "$LOG_DIR/before.json" "$LOG_DIR/after.json"
  echo "$STAMP MAX score=$SCORE_BEFORE/$MAX" >> "$TIMELINE"
  exit 0
fi

log "running claude -p (model=$CLAUDE_MODEL, max-turns=$MAX_TURNS) ..."
cd "$HERE"
"$CLAUDE_BIN" -p \
  --model "$CLAUDE_MODEL" \
  --max-turns "$MAX_TURNS" \
  --output-format text \
  < program.md \
  > "$LOG_DIR/agent_transcript.txt" 2>&1 || log "claude -p exited non-zero (check transcript)"

# ─────────────────────────────────────────────────────────────────────
# 6. Re-measure and log the delta
# ─────────────────────────────────────────────────────────────────────

"$HERE/measure.sh" > "$LOG_DIR/after.json" 2>&1 || err "measure failed after iteration"
SCORE_AFTER=$(jq -r .score "$LOG_DIR/after.json")
PCT_AFTER=$(jq -r .percent "$LOG_DIR/after.json")
DELTA=$((SCORE_AFTER - SCORE_BEFORE))

if [[ "$DELTA" -gt 0 ]]; then
  log "✓ improved by $DELTA: $SCORE_BEFORE -> $SCORE_AFTER (${PCT_AFTER}%)"
  echo "$STAMP IMPROVED $SCORE_BEFORE->$SCORE_AFTER (+$DELTA)" >> "$TIMELINE"
  # Capture diff for human review
  cd "$PATIENT"
  git diff > "$LOG_DIR/diff.patch" 2>/dev/null
  log "diff saved to $LOG_DIR/diff.patch — review and commit manually"
elif [[ "$DELTA" -lt 0 ]]; then
  log "✗ REGRESSED by $((-DELTA)): $SCORE_BEFORE -> $SCORE_AFTER. Reverting."
  cd "$PATIENT"
  git checkout -- . 2>/dev/null
  echo "$STAMP REGRESSED $SCORE_BEFORE->$SCORE_AFTER (reverted)" >> "$TIMELINE"
else
  log "─ no change: score still $SCORE_AFTER. Iteration kept (may have made non-scoring progress)."
  echo "$STAMP NOCHANGE $SCORE_AFTER" >> "$TIMELINE"
fi

log "tick complete"
exit 0
