#!/usr/bin/env bash
#
# opencode-builder.sh
#
# Launches an OpenCode server, creates a session with the product-builder
# agent, and continuously sends "continue" messages in an infinite loop.
#
# Signal handling:
#   Ctrl+C once  — finishes the in-flight request, then stops the loop
#   Ctrl+C twice — (within 2 seconds) kills the request and exits immediately
#

set -uo pipefail

# ── Configuration (override via environment) ──────────────────────────────
PORT="${OPENCODE_PORT:-4096}"
HOST="${OPENCODE_HOST:-127.0.0.1}"
BASE="http://${HOST}:${PORT}"
AGENT="product-builder"
PROMPT="Complete the current project's milestones up to and including v3.7.0 to a production ready state"
HEALTH_TIMEOUT=60  # seconds to wait for the server to become healthy

# ── Internal state ────────────────────────────────────────────────────────
SERVER_PID=""
CURL_PID=""
SESSION_ID=""
STOP_LOOP=false
LAST_SIGINT=0
OWN_SERVER=false

# ── Helpers ───────────────────────────────────────────────────────────────
die() { printf "ERROR: %s\n" "$*" >&2; exit 1; }
log() { printf "[%s] %s\n" "$(date +%H:%M:%S)" "$*"; }

check_opencode_running() {
  # Check if port is open and responding as OpenCode server
  if curl -sf "${BASE}/global/health" >/dev/null 2>&1; then
    return 0  # OpenCode server is running
  else
    return 1  # No OpenCode server detected
  fi
}

cleanup() {
  if $OWN_SERVER && [[ -n "$SERVER_PID" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then
    log "Stopping opencode server (PID $SERVER_PID)..."
    kill "$SERVER_PID" 2>/dev/null
    wait "$SERVER_PID" 2>/dev/null || true
  elif ! $OWN_SERVER; then
    log "Leaving existing OpenCode server running (not managed by this script)"
  fi
}

handle_sigint() {
  local now
  now=$(date +%s)

  # Second Ctrl+C within 2 seconds → force-quit
  if $STOP_LOOP && (( now - LAST_SIGINT <= 2 )); then
    printf "\nForce quit!\n"
    [[ -n "$CURL_PID" ]] && kill "$CURL_PID" 2>/dev/null || true
    cleanup
    exit 130
  fi

  # First Ctrl+C → graceful stop after current request
  STOP_LOOP=true
  LAST_SIGINT=$now
  printf "\nWill stop after the current request completes. Ctrl+C again within 2s to force-quit.\n"
}

trap handle_sigint INT
trap cleanup EXIT

# ── Preflight checks ─────────────────────────────────────────────────────
for cmd in curl jq opencode; do
  command -v "$cmd" &>/dev/null || die "'$cmd' is required but not found in PATH"
done

# ── Start the opencode server ─────────────────────────────────────────────
# Check if OpenCode server is already running
if check_opencode_running; then
  echo "┌─────────────────────────────────────────────────────────────────┐"
  echo "│ ⚠️  WARNING: OpenCode server already running on ${HOST}:${PORT}"
  echo "│ 🔗 Connecting to existing server instead of starting new one"
  echo "│ 💡 The script will NOT manage this server's lifecycle"
  echo "└─────────────────────────────────────────────────────────────────┘"
  echo ""
  log "Using existing OpenCode server at ${HOST}:${PORT}"
  OWN_SERVER=false
else
  # Start our own server as before
  log "Starting opencode server on ${HOST}:${PORT}..."
  opencode serve --port "$PORT" --hostname "$HOST" &
  SERVER_PID=$!
  OWN_SERVER=true
  
  log "Waiting for server to become healthy (up to ${HEALTH_TIMEOUT}s)..."
  healthy=false
  for (( i = 1; i <= HEALTH_TIMEOUT; i++ )); do
    if curl -sf "${BASE}/global/health" >/dev/null 2>&1; then
      healthy=true
      break
    fi
    kill -0 "$SERVER_PID" 2>/dev/null || die "Server process exited unexpectedly"
    sleep 1
  done
  $healthy || die "Server did not become healthy within ${HEALTH_TIMEOUT}s"
  log "Server is ready."
fi

# ── Create a session ──────────────────────────────────────────────────────
log "Creating session..."
SESSION_ID=$(
  curl -sf -X POST "${BASE}/session" \
    -H "Content-Type: application/json" \
    -d '{"title":"product-builder-auto"}' \
  | jq -r '.id'
) || die "Failed to create session"

[[ -n "$SESSION_ID" && "$SESSION_ID" != "null" ]] || die "Session ID is empty or null"
log "Session ID: ${SESSION_ID}"

# ── send_message ──────────────────────────────────────────────────────────
# Sends a JSON body to POST /session/:id/message and blocks until the LLM
# responds.  curl is run in a subprocess that ignores SIGINT (SIG_IGN is
# inherited across exec), so a single Ctrl+C cannot kill the in-flight
# request.  The parent re-waits whenever its own trap handler interrupts
# the `wait` builtin.
send_message() {
  local body=$1

  ( trap '' INT
    exec curl -sf -X POST "${BASE}/session/${SESSION_ID}/message" \
      -H "Content-Type: application/json" \
      -d "$body" \
      -o /dev/null
  ) &
  CURL_PID=$!

  # Keep waiting until curl actually exits — `wait` can return early when
  # our SIGINT handler fires, so we loop on kill -0 to re-wait.
  while kill -0 "$CURL_PID" 2>/dev/null; do
    wait "$CURL_PID" 2>/dev/null || true
  done
  CURL_PID=""
}

# ── Send the initial prompt ──────────────────────────────────────────────
INIT_BODY=$(jq -nc --arg a "$AGENT" --arg t "$PROMPT" \
  '{agent: $a, parts: [{type: "text", text: $t}]}')

log "Sending initial prompt (agent: ${AGENT})..."
log "Prompt: ${PROMPT}"
echo ""
send_message "$INIT_BODY"
log "Initial prompt complete."

# ── Continue loop ─────────────────────────────────────────────────────────
CONT_BODY=$(jq -nc '{parts: [{type: "text", text: "continue"}]}')
N=0

while ! $STOP_LOOP; do
  N=$((N + 1))
  log "── continue #${N} ──"
  send_message "$CONT_BODY"
  log "continue #${N} complete."
done

echo ""
log "Loop stopped. Shutting down."
exit 0
