#!/usr/bin/env bash
# launch-kit MCP chip generator.
# Emits a colored one-line summary of MCP daemon health, one chip per daemon.
# Composes into any existing statusline via the launch-kit wrapper.
#
# Usage:
#   statusline-mcp.sh                       # all known chips (recall, chart, deck, council, secure)
#   statusline-mcp.sh --show=recall,chart   # only the listed chips
#   statusline-mcp.sh --compact             # collapse to `mcp <up>/<total>`
#                                             green when all up, red if any down
#
# `secure` differs from the others: recall/chart/deck/council are local
# daemons probed via a pidfile/lockfile/freshness file, but launch-secure is
# the hosted MCP. Its chip is a reachability probe (curl) against the active
# profile's serverUrl, cached for SECURE_CACHE_TTL seconds so the statusline
# never blocks on the network on every render.
#
# Project root is inferred from $LK_STATUSLINE_CWD (set by the wrapper) or
# $PWD by walking up until a `.launchsecure` dir is found. Silent exit when
# the cwd is not inside a launchsecure project.
set -u

show=""
compact=0
for arg in "$@"; do
  case "$arg" in
    --show=*) show="${arg#--show=}" ;;
    --compact) compact=1 ;;
  esac
done
[ -z "$show" ] && show="recall,chart,deck,council,secure"

# TTL for the cached launch-secure reachability probe (seconds). The statusline
# re-renders far too often to curl on every paint; one live probe per window is
# plenty for an "is the host up" signal.
SECURE_CACHE_TTL=30

GREEN=$'\033[32m'
ORANGE=$'\033[33m'
RED=$'\033[31m'
RESET=$'\033[0m'

find_project_root() {
  # `.recall` only exists at the repo root (launch-recall init creates it
  # there). `.launchsecure` can appear in subdirs too (e.g. packages/cli
  # when graphs are generated for a sub-project), so we prefer .recall as
  # the canonical root marker. Fallback: outermost `.launchsecure` ancestor
  # (walk up and keep overwriting → keeps the highest match).
  local d="${1:-$PWD}"
  local recall_root=""
  local outermost_launchsecure=""
  while [ -n "$d" ] && [ "$d" != "/" ]; do
    if [ -z "$recall_root" ] && [ -d "$d/.recall" ]; then
      recall_root="$d"
    fi
    if [ -d "$d/.launchsecure" ]; then
      outermost_launchsecure="$d"
    fi
    d=$(dirname "$d")
  done
  if [ -n "$recall_root" ]; then echo "$recall_root"; return 0; fi
  if [ -n "$outermost_launchsecure" ]; then echo "$outermost_launchsecure"; return 0; fi
  return 1
}

PROJECT_ROOT=$(find_project_root "${LK_STATUSLINE_CWD:-$PWD}") || exit 0

iso_to_epoch() {
  # Input is always UTC (ISO timestamps from .freshness.json end in Z).
  # macOS BSD `date` defaults to local TZ, so without TZ=UTC the timestamp
  # gets misread as local time → wildly wrong age (e.g. 6h instead of 55m).
  local ts="$1"
  ts="${ts%%.*}"; ts="${ts%%Z*}"; ts="${ts%%+*}"
  TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$ts" +%s 2>/dev/null
}

fmt_age() {
  local then="$1"
  local now diff
  now=$(date +%s)
  diff=$((now - then))
  if [ "$diff" -lt 60 ]; then echo "${diff}s"
  elif [ "$diff" -lt 3600 ]; then echo "$((diff/60))m"
  elif [ "$diff" -lt 86400 ]; then echo "$((diff/3600))h"
  else echo "$((diff/86400))d"
  fi
}

pid_from_lockfile() {
  grep -o '"pid"[^,}]*' "$1" 2>/dev/null | head -1 | sed 's/[^0-9]//g'
}

# Each chip_* function sets two globals so the caller can use both the
# colored display string (verbose mode) and the abstract state (compact mode)
# without re-running the underlying probe. Must be called as a bare command
# (no subshell) so the assignments survive.
_state=""
_display=""

chip_recall() {
  local pidfile="$PROJECT_ROOT/.recall/watch.pid"
  local repo="$PROJECT_ROOT/.recall/repo.git"
  if [ ! -f "$pidfile" ]; then _state="red"; _display="${RED}recall${RESET}"; return; fi
  local pid
  pid=$(cat "$pidfile" 2>/dev/null) || pid=""
  if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
    _state="red"; _display="${RED}recall${RESET}"
    return
  fi
  if [ -d "$repo" ]; then
    local last
    last=$(git --git-dir="$repo" log -1 --format=%ct 2>/dev/null)
    if [ -n "$last" ]; then
      local now diff age
      now=$(date +%s); diff=$((now - last)); age=$(fmt_age "$last")
      if [ "$diff" -gt 21600 ]; then
        _state="orange"; _display="${ORANGE}recall(${age})${RESET}"
      else
        _state="green"; _display="${GREEN}recall(${age})${RESET}"
      fi
      return
    fi
  fi
  _state="green"; _display="${GREEN}recall${RESET}"
}

chip_chart() {
  # Drift-based color — reflects how out-of-sync the graph is with the code,
  # NOT whether a watcher daemon is alive. A user who regenerated by hand
  # and never started the watcher should still see green when the graph is
  # fresh. Daemon liveness lives in /kit:show-mcp-status.
  #
  #   RED    — .freshness.json missing OR failedFiles non-empty (broken graph)
  #   ORANGE — state != "fresh" (chart MCP says the snapshot has drifted)
  #   GREEN  — state == "fresh" AND no failed files
  local freshness="$PROJECT_ROOT/.launchsecure/graphs/.freshness.json"
  if [ ! -f "$freshness" ]; then _state="red"; _display="${RED}chart${RESET}"; return; fi

  local last state_field
  last=$(grep -o '"lastFullRegenAt"[^,}]*' "$freshness" | head -1 | sed 's/.*"\([0-9TZ:.+-]*\)".*/\1/')
  state_field=$(grep -o '"state"[[:space:]]*:[[:space:]]*"[^"]*"' "$freshness" | head -1 | sed 's/.*"\([^"]*\)"$/\1/')

  # failedFiles is non-empty iff the empty-array literal `[]` isn't present.
  local color state
  if ! grep -q '"failedFiles"[[:space:]]*:[[:space:]]*\[[[:space:]]*\]' "$freshness"; then
    color="$RED"; state="red"
  elif [ "$state_field" != "fresh" ]; then
    color="$ORANGE"; state="orange"
  else
    color="$GREEN"; state="green"
  fi

  if [ -n "$last" ] && [ "$last" != "null" ]; then
    local epoch age
    epoch=$(iso_to_epoch "$last")
    if [ -n "$epoch" ]; then
      age=$(fmt_age "$epoch")
      _state="$state"; _display="${color}chart(${age})${RESET}"
      return
    fi
  fi
  _state="$state"; _display="${color}chart${RESET}"
}

chip_deck() {
  local lock="$PROJECT_ROOT/.launchsecure/launch-deck.lock"
  if [ -f "$lock" ]; then
    local pid; pid=$(pid_from_lockfile "$lock")
    if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
      _state="green"; _display="${GREEN}deck${RESET}"; return
    fi
  fi
  _state="orange"; _display="${ORANGE}deck${RESET}"
}

chip_council() {
  local lock="$PROJECT_ROOT/.launchsecure/launch-council.lock"
  if [ -f "$lock" ]; then
    local pid; pid=$(pid_from_lockfile "$lock")
    if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
      _state="green"; _display="${GREEN}council${RESET}"; return
    fi
  fi
  _state="orange"; _display="${ORANGE}council${RESET}"
}

# Pull the active profile's serverUrl out of .launch-secure.cred.config without
# jq (the chip script stays coreutils-only). Handles both shapes the cred file
# can take — profiled ({active, profiles:{<name>:{serverUrl}}}) and flat
# ({serverUrl}) — mirroring the .mcp.json headersHelper's own fallback.
extract_secure_url() {
  local cred="$1" active
  active=$(grep -o '"active"[[:space:]]*:[[:space:]]*"[^"]*"' "$cred" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
  awk -v prof="$active" '
    function opens(s){ return gsub(/{/,"&",s) }
    function closes(s){ return gsub(/}/,"&",s) }
    BEGIN { inprof = (prof == "") ? 1 : 0; depth = 0 }
    {
      if (prof != "" && !inprof) {
        if (index($0, "\"" prof "\"") > 0 && index($0, "{") > 0) { inprof = 1; depth = opens($0) - closes($0) }
        next
      }
      if (match($0, /"serverUrl"[[:space:]]*:[[:space:]]*"[^"]*"/)) {
        s = substr($0, RSTART, RLENGTH)
        sub(/.*:[[:space:]]*"/, "", s); sub(/".*/, "", s)
        print s; exit
      }
      if (prof != "") { depth += opens($0) - closes($0); if (depth <= 0) exit }
    }
  ' "$cred" 2>/dev/null
}

_render_secure() {
  # $1 = state (green|red|unknown), $2 = label (active profile name)
  case "$1" in
    green)   _state="green";  _display="${GREEN}secure(${2})${RESET}" ;;
    unknown) _state="orange"; _display="${ORANGE}secure(${2})${RESET}" ;;
    *)       _state="red";    _display="${RED}secure(${2})${RESET}" ;;
  esac
}

chip_secure() {
  local cred="$PROJECT_ROOT/.launch-secure.cred.config"
  if [ ! -f "$cred" ]; then _state="orange"; _display="${ORANGE}secure(?)${RESET}"; return; fi

  local active url label
  active=$(grep -o '"active"[[:space:]]*:[[:space:]]*"[^"]*"' "$cred" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
  url=$(extract_secure_url "$cred")
  if [ -z "$url" ]; then _state="orange"; _display="${ORANGE}secure(?)${RESET}"; return; fi
  label="${active:-secure}"

  # Cache keyed on the URL so a course-switch (prod↔local) invalidates instantly.
  local cache="$PROJECT_ROOT/.launchsecure/.statusline-secure.cache"
  local now; now=$(date +%s)
  if [ -f "$cache" ]; then
    local c_ts c_url c_state
    IFS='|' read -r c_ts c_url c_state < "$cache" 2>/dev/null || true
    if [ -n "${c_ts:-}" ] && [ "${c_url:-}" = "$url" ] && [ $((now - c_ts)) -lt "$SECURE_CACHE_TTL" ]; then
      _render_secure "$c_state" "$label"; return
    fi
  fi

  # Live probe. Any HTTP status — including 401/403/404/405 — means the host is
  # reachable; only a transport failure (curl writes 000) counts as down. No
  # curl on PATH → unknown (orange), never a false red.
  local state="unknown" code
  if command -v curl >/dev/null 2>&1; then
    code=$(curl -s -o /dev/null -m 2 -w '%{http_code}' "$url" 2>/dev/null || echo 000)
    if [ -n "$code" ] && [ "$code" != "000" ]; then state="green"; else state="red"; fi
  fi
  printf '%s|%s|%s\n' "$now" "$url" "$state" > "$cache" 2>/dev/null || true
  _render_secure "$state" "$label"
}

show_list=$(echo "$show" | tr ',' ' ')
total=0
up=0
out=""
for d in $show_list; do
  case "$d" in
    recall) chip_recall ;;
    chart)  chip_chart ;;
    deck)   chip_deck ;;
    council) chip_council ;;
    secure) chip_secure ;;
    *) continue ;;
  esac
  total=$((total + 1))
  [ "$_state" = "green" ] && up=$((up + 1))
  if [ "$compact" = "0" ]; then
    if [ -z "$out" ]; then out="$_display"; else out="${out} · ${_display}"; fi
  fi
done

if [ "$compact" = "1" ]; then
  if [ "$total" -eq 0 ]; then exit 0; fi
  if [ "$up" -eq "$total" ]; then
    printf '%smcp %d/%d%s' "$GREEN" "$up" "$total" "$RESET"
  else
    printf '%smcp %d/%d%s' "$RED" "$up" "$total" "$RESET"
  fi
else
  printf '%s' "$out"
fi
