#!/usr/bin/env bash
# ayf-heartbeat -- liveness heartbeat for AY Framework worker agents.
#
# A worker proves it is still alive by "touching" its task lock file at every
# phase boundary (PLAN -> BUILD -> REVIEW -> QA -> SHIP). The lock's mtime is the
# liveness signal: `ayf-fleet` (task #81) reports any lock whose mtime is older
# than the fleet's --stale-seconds threshold as STALLED, so the operator (#84)
# can re-dispatch a dead worker.
#
# The worker's session is identified by $CLAUDE_CODE_SESSION_ID; the matching
# lock is the one in .ay/tracking/locks/ whose `session:` field equals that id.
#
# Usage -- source this file, then beat at each phase boundary:
#     source "$(dirname "$0")/ayf-heartbeat"     # or: source bin/ayf-heartbeat
#     ayf_heartbeat            # refresh my lock's mtime now
#     ayf_heartbeat BUILD      # refresh mtime AND record `phase: BUILD` in the lock
#
# Or run it directly as a one-shot:
#     bin/ayf-heartbeat            # touch my lock
#     bin/ayf-heartbeat REVIEW     # touch my lock and set phase to REVIEW
#
# Exit / return status: 0 on success; non-zero if the session id is unset or no
# lock owned by this session was found.

# Walk up from $PWD looking for a `.ay` directory; fall back to ~/.ay.
_ayf_find_ay_dir() {
  local dir="$PWD"
  while [ "$dir" != "/" ]; do
    if [ -d "$dir/.ay" ]; then
      printf '%s\n' "$dir/.ay"
      return 0
    fi
    dir="$(dirname "$dir")"
  done
  [ -d "$HOME/.ay" ] && { printf '%s\n' "$HOME/.ay"; return 0; }
  return 1
}

# Print the path to this session's lock file, or return non-zero if none.
ayf_heartbeat_lockfile() {
  local sid="${CLAUDE_CODE_SESSION_ID:-}"
  if [ -z "$sid" ]; then
    echo "ayf_heartbeat: CLAUDE_CODE_SESSION_ID is not set" >&2
    return 2
  fi

  local ay_dir
  ay_dir="$(_ayf_find_ay_dir)" || {
    echo "ayf_heartbeat: no .ay directory found (run inside an AY Framework project)" >&2
    return 3
  }

  local locks_dir="$ay_dir/tracking/locks"
  [ -d "$locks_dir" ] || locks_dir="$ay_dir/locks"   # legacy fallback

  local f
  for f in "$locks_dir"/*.lock; do
    [ -e "$f" ] || continue
    # Match `session: <id>` or `session=<id>` (tolerate CRLF / surrounding space).
    if grep -qiE "^[[:space:]]*session[[:space:]]*[:=][[:space:]]*${sid}[[:space:]]*$" "$f"; then
      printf '%s\n' "$f"
      return 0
    fi
  done

  echo "ayf_heartbeat: no lock owned by session $sid in $locks_dir" >&2
  return 1
}

# Refresh this session's lock mtime (liveness proof). Optional arg: the phase to
# record (updates or appends a `phase:` line so ayf-fleet can classify the agent
# as PLANNING vs BUILDING).
ayf_heartbeat() {
  local phase="${1:-}"
  local lockfile
  lockfile="$(ayf_heartbeat_lockfile)" || return $?

  if [ -n "$phase" ]; then
    if grep -qiE "^[[:space:]]*phase[[:space:]]*[:=]" "$lockfile"; then
      # Replace the existing phase line in place (portable sed: temp file).
      local tmp="${lockfile}.tmp.$$"
      sed -E "s/^[[:space:]]*phase[[:space:]]*[:=].*/phase: ${phase}/I" "$lockfile" > "$tmp" \
        && mv "$tmp" "$lockfile"
    else
      printf 'phase: %s\n' "$phase" >> "$lockfile"
    fi
  fi

  # Step 2: bump the mtime. This is the heartbeat that ayf-fleet reads.
  touch "$lockfile"
}

# When executed directly (not sourced), perform a single heartbeat.
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
  ayf_heartbeat "${1:-}"
  exit $?
fi
