#!/bin/bash
set -euo pipefail

# Patchcord subscribe for Kimi CLI — background polling task.
# Kimi has no native push/WebSocket listener, so we poll the inbox
# and exit when messages arrive. Exiting triggers Kimi's auto-run
# (if the session is armed), waking the agent to read and reply.
#
# Usage: patchcord subscribe        (starts with default 30s interval)
#        patchcord subscribe 10     (starts with 10s interval)
#
# Resolves per-project .kimi-code/mcp.json only (walks up from cwd).
# No global fallback.

command -v jq >/dev/null 2>&1 || { echo "jq required" >&2; exit 1; }

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
if [ -f "$SCRIPT_DIR/lib/runtime-dir.sh" ]; then
  # shellcheck source=lib/runtime-dir.sh
  . "$SCRIPT_DIR/lib/runtime-dir.sh"
elif [ -f "$SCRIPT_DIR/runtime-dir.sh" ]; then
  . "$SCRIPT_DIR/runtime-dir.sh"
fi
RUNTIME_DIR=$(pc_runtime_dir 2>/dev/null || exit 1)

# Resolve MCP config: PROJECT-scoped ONLY (walk up from cwd for .kimi-code/mcp.json).
# Legacy .kimi/mcp.json (Python kimi-cli) is NOT read — reinstall/provision removes it.
# NO global fallback: auto-launched by the (global) SessionStart hook on EVERY
# Kimi session, so a global fallback would start a listener for some leftover
# identity in every folder. No project config in the cwd tree => exit quietly
# (0) unless PATCHCORD_KIMI_REQUIRED=1 (explicit `patchcord subscribe --kimi`).
KIMI_MCP=""
dir="$PWD"
while [ "$dir" != "/" ]; do
  if [ -f "$dir/.kimi-code/mcp.json" ]; then
    KIMI_MCP="$dir/.kimi-code/mcp.json"
    break
  fi
  dir=$(dirname "$dir")
done

if [ -z "$KIMI_MCP" ] || [ ! -f "$KIMI_MCP" ]; then
  if [ "${PATCHCORD_KIMI_REQUIRED:-}" = "1" ]; then
    echo "No Kimi Code Patchcord config in this project tree (.kimi-code/mcp.json)." >&2
    echo "Run patchcord provision <agent> --tool kimi --namespace <ns> from the project root." >&2
    exit 1
  fi
  exit 0
fi

TOKEN=$(jq -r '.mcpServers.patchcord.headers.Authorization // empty' "$KIMI_MCP" 2>/dev/null | sed 's/^Bearer //i' || true)
URL=$(jq -r '.mcpServers.patchcord.url // empty' "$KIMI_MCP" 2>/dev/null || true)

if [ -z "$URL" ] || [ -z "$TOKEN" ]; then
  echo "Patchcord not configured in $KIMI_MCP" >&2
  exit 1
fi

BASE_URL=$(echo "$URL" | sed 's|/mcp$||; s|/mcp/bearer$||')

# Poll loop: count_only + no registry write — same cost model as subscribe.mjs
# drainQueueOnce (id SELECT only). Kimi cannot hold a WebSocket/Realtime
# listener (no Monitor; task must exit to wake), so polling is the transport;
# the bug was fetching full message bodies every tick.
INBOX_POLL_QS="status=pending&limit=5&count_only=1&touch_presence=0"

# Derive agent identity for pidfile (count_only, no registry write)
IDENTITY_RESP=$(curl -s --max-time 5 \
  -H "Authorization: Bearer ${TOKEN}" \
  "${BASE_URL}/api/inbox?limit=0&count_only=1&touch_presence=0" 2>/dev/null || echo "{}")
NAMESPACE_ID=$(echo "$IDENTITY_RESP" | jq -r '.namespace_id // empty' 2>/dev/null || true)
AGENT_ID=$(echo "$IDENTITY_RESP" | jq -r '.agent_id // empty' 2>/dev/null || true)

if [ -z "$NAMESPACE_ID" ] || [ -z "$AGENT_ID" ]; then
  echo "Could not determine agent identity — check your token" >&2
  exit 1
fi

PIDFILE="$RUNTIME_DIR/patchcord_subscribe_${NAMESPACE_ID}_${AGENT_ID}.pid"
LEGACY_PIDFILE="/tmp/patchcord_subscribe_${NAMESPACE_ID}_${AGENT_ID}.pid"
NOTIFY_FILE="${KIMI_CODE_HOME:-${HOME}/.kimi-code}/patchcord-subscribe-notify.txt"

# Always replace any existing listener for this project. Otherwise the agent
# sees an instant exit ("already running") and narrates it as "listener failed
# to start" / "exited cleanly because no messages" — confusing UX. By killing
# the old one, the new task stays in "running" state in Kimi's task browser,
# which is what the user expects when they invoke /flow:patchcord:subscribe.
OLD_PIDFILE="$PIDFILE"
[ -f "$OLD_PIDFILE" ] || OLD_PIDFILE="$LEGACY_PIDFILE"
if [ -f "$OLD_PIDFILE" ]; then
  OLD_PID=$(cat "$OLD_PIDFILE" 2>/dev/null || echo "")
  if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
    kill "$OLD_PID" 2>/dev/null || true
    for _ in 1 2 3 4 5; do
      kill -0 "$OLD_PID" 2>/dev/null || break
      sleep 0.2
    done
  fi
fi
echo $$ > "$PIDFILE"
echo $$ > "$LEGACY_PIDFILE"

# Cleanup only advisory pidfiles owned by this process. The socket lock used by
# subscribe.mjs is authoritative; these files are compatibility hints only.
cleanup() {
  for path in "$PIDFILE" "$LEGACY_PIDFILE"; do
    [ "$(cat "$path" 2>/dev/null || true)" = "$$" ] && rm -f "$path"
  done
}
trap cleanup EXIT INT TERM

# Poll interval: first arg or default 30s
INTERVAL="${1:-30}"
if ! [[ "$INTERVAL" =~ ^[0-9]+$ ]] || [ "$INTERVAL" -lt 1 ]; then
  INTERVAL=30
fi

echo "PATCHCORD: subscribe started (poll ${INTERVAL}s) for ${AGENT_ID}@${NAMESPACE_ID}"

while true; do
  RESP=$(curl -s --max-time 10 \
    -H "Authorization: Bearer ${TOKEN}" \
    "${BASE_URL}/api/inbox?${INBOX_POLL_QS}" 2>/dev/null || echo "{}")

  COUNT=$(echo "$RESP" | jq -r '.pending_count // 0' 2>/dev/null || echo "0")

  if [ "$COUNT" -gt 0 ] 2>/dev/null; then
    # Write notification file so the agent sees context on wake
    echo "PATCHCORD: ${COUNT} message(s) waiting" > "$NOTIFY_FILE"
    # Kimi auto-triggers a new agent turn when ANY background task reaches a
    # terminal state — exit code doesn't matter. Use exit 0 so Kimi shows
    # "completed" (not "failed"), and emit the wake-up text to stdout so it
    # appears in the agent's task output preview.
    printf '%s\n' "PATCHCORD WAKE-UP: ${COUNT} pending message(s) for this agent. You MUST do all three steps now without asking the user: (1) call the inbox MCP tool, (2) reply to each message with what you did, (3) re-arm the listener by calling the Shell tool with command='patchcord subscribe --kimi 5', run_in_background=true, timeout=86400, description='Patchcord listener'. This is not optional — the listener stopped because messages arrived, and steps 1-3 are how you resume monitoring."
    exit 0
  fi

  sleep "$INTERVAL"
done
