#!/usr/bin/env bash
# update-check.sh  -  cached version check (Phase 0 Step 0.6)
#
# Compares the locally installed pipeline version against the npm registry's
# dist-tags. Cached with a TTL so at most one network call per TTL window; the
# call is bounded by a short timeout and every failure path is silent  -  this
# script NEVER blocks and NEVER fails the pipeline (exit code is always 0).
#
# Two tags are read:
#   dist-tags.latest    newest published release  -> advisory "update available"
#   dist-tags.required  minimum version a user may run -> forced-update signal
#
# `required` is opt-in per release and set out of band, without republishing:
#   npm dist-tag add @<scope>/multi-agent-pipeline@<version> required
# Absent tag = no floor = advisory-only behaviour, exactly as before.
#
# stdout:
#   ""                        already current (or local ahead of the registry)
#   "<local>|<latest>"        a newer version exists  -  advisory
#   "<local>|<latest>|force"  local is BELOW dist-tags.required  -  the caller
#                             must halt the run until /multi-agent:update ran
# Exit code: always 0. Enforcement is the caller's job  -  see
# pipeline/scripts/require-supported-version.sh, which turns "force" into a
# non-zero exit for shell callers.
#
# Usage:
#   bash pipeline/scripts/update-check.sh                 # auto-detect local version
#   bash pipeline/scripts/update-check.sh --local 10.8.0  # explicit local version
#   bash pipeline/scripts/update-check.sh --ttl-hours 24  # cache window (default 24)
#   bash pipeline/scripts/update-check.sh --force         # ignore cache
#   bash pipeline/scripts/update-check.sh --print-required  # emit the floor only
#
# Cache file: ~/.claude/logs/multi-agent/.update-check ("epoch|latest|required").
# A legacy two-field cache ("epoch|latest") still reads  -  the missing third
# field means "no floor known", never "no floor exists".
# Registry read is a plain curl  -  never `npm view` (a user-level .npmrc scope
# mapping can silently reroute npm to a different registry; curl cannot lie).

set -euo pipefail

PKG="@mmerterden/multi-agent-pipeline"
REGISTRY_URL="https://registry.npmjs.org/${PKG/\//%2F}"
CACHE_FILE="${UPDATE_CHECK_CACHE:-$HOME/.claude/logs/multi-agent/.update-check}"
TTL_HOURS=24
LOCAL_VERSION=""
FORCE=0
PRINT_REQUIRED=0

while [ $# -gt 0 ]; do
  case "$1" in
    --local) LOCAL_VERSION="${2:-}"; shift 2 ;;
    --ttl-hours) TTL_HOURS="${2:-24}"; shift 2 ;;
    --force) FORCE=1; shift ;;
    --print-required) PRINT_REQUIRED=1; shift ;;
    *) shift ;;
  esac
done

# Local version: explicit arg, else read from the pipeline repo clone.
if [ -z "$LOCAL_VERSION" ]; then
  for candidate in "$HOME/multi-agent-pipeline" "$HOME/dev/multi-agent-pipeline" "$HOME/projects/multi-agent-pipeline"; do
    if [ -f "$candidate/package.json" ]; then
      LOCAL_VERSION=$(node -p "require('$candidate/package.json').version" 2>/dev/null || true)
      [ -n "$LOCAL_VERSION" ] && break
    fi
  done
fi
# npx-only installs have no repo clone; the installer stamps the version here.
if [ -z "$LOCAL_VERSION" ]; then
  for marker in "$HOME/.claude/.pipeline-version" "$HOME/.copilot/.pipeline-version" \
                "$HOME/.codex/.pipeline-version"; do
    if [ -f "$marker" ]; then
      LOCAL_VERSION=$(head -1 "$marker" 2>/dev/null | tr -d '[:space:]')
      [ -n "$LOCAL_VERSION" ] && break
    fi
  done
fi
[ -z "$LOCAL_VERSION" ] && exit 0  # cannot determine local version -> silent no-op

# Highest of two dotted versions; ties resolve to the second argument.
highest_of() {
  printf '%s\n%s\n' "$1" "$2" | sort -t. -k1,1n -k2,2n -k3,3n | tail -1
}

now=$(date +%s)
latest=""
required=""

# Fresh cache?
if [ "$FORCE" -eq 0 ] && [ -f "$CACHE_FILE" ]; then
  cached_epoch=$(cut -d'|' -f1 "$CACHE_FILE" 2>/dev/null || echo 0)
  cached_latest=$(cut -d'|' -f2 "$CACHE_FILE" 2>/dev/null || echo "")
  cached_required=$(cut -d'|' -f3 "$CACHE_FILE" 2>/dev/null || echo "")
  case "$cached_epoch" in (*[!0-9]*|"") cached_epoch=0 ;; esac
  if [ $((now - cached_epoch)) -lt $((TTL_HOURS * 3600)) ] && [ -n "$cached_latest" ]; then
    latest="$cached_latest"
    required="$cached_required"
  fi
fi

# Stale or missing cache -> one bounded registry call (silent on any failure).
# The abbreviated packument carries dist-tags at a quarter of the full document.
if [ -z "$latest" ]; then
  tags=$(curl -sm 3 -H 'Accept: application/vnd.npm.install-v1+json' "$REGISTRY_URL" 2>/dev/null \
    | { if command -v jq >/dev/null 2>&1; then
          jq -r '[."dist-tags".latest // "", ."dist-tags".required // ""] | join("|")'
        else
          node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const t=JSON.parse(s)["dist-tags"]||{};process.stdout.write(`${t.latest||""}|${t.required||""}`)}catch{}})'
        fi; } ) || true
  latest="${tags%%|*}"
  required="${tags#*|}"
  [ "$required" = "$tags" ] && required=""
  [ -z "$latest" ] && exit 0
  mkdir -p "$(dirname "$CACHE_FILE")" 2>/dev/null || exit 0
  printf '%s|%s|%s\n' "$now" "$latest" "$required" > "$CACHE_FILE" 2>/dev/null || true
fi

# A floor above latest is a publisher mistake (the `required` tag was moved to a
# version that `latest` no longer covers). Clamp rather than brick every user.
if [ -n "$required" ] && [ "$(highest_of "$required" "$latest")" != "$latest" ]; then
  required="$latest"
fi

if [ "$PRINT_REQUIRED" -eq 1 ]; then
  printf '%s\n' "$required"
  exit 0
fi

# Forced update: local sorts strictly BELOW the required floor.
if [ -n "$required" ] && [ "$LOCAL_VERSION" != "$required" ] \
   && [ "$(highest_of "$LOCAL_VERSION" "$required")" = "$required" ]; then
  printf '%s|%s|force\n' "$LOCAL_VERSION" "$latest"
  exit 0
fi

[ "$latest" = "$LOCAL_VERSION" ] && exit 0

# Update available only when latest sorts strictly ABOVE local (a dev machine
# running ahead of the registry must not see an "update" prompt).
if [ "$(highest_of "$LOCAL_VERSION" "$latest")" = "$latest" ]; then
  printf '%s|%s\n' "$LOCAL_VERSION" "$latest"
fi
exit 0
