#!/usr/bin/env bash
#
# probe-evidence-capability.sh  -  measure what visual evidence this machine and
# this repo can actually produce, BEFORE the user is asked to choose a test depth.
#
# Contract: multi-agent-refs/features/visual-evidence.md section 4.
#
# The order is probe, then question, then run. Offering "unit + UI test with a
# screen recording" and only then discovering there is no UI test target, or no
# booted simulator, spends the user's answer on something that cannot happen. So
# the options are built from what this prints.
#
# Three rules this file exists to keep:
#
#   1. Absence carries a reason. Every empty value is paired with a *_REASON, so
#      a closed option can say why it is closed instead of vanishing from the menu.
#   2. Unmeasurable is `unknown`, never `false`. A probe that could not look and a
#      probe that looked and found nothing are different facts, and collapsing
#      them lets a missing tool read as a clean negative.
#   3. Detection is not reimplemented here. The UI test answer comes from
#      run-ui-tests.sh detect, which is also what actually runs the tests; a
#      second copy is a second place for the answer to drift.
#
# Usage:
#   probe-evidence-capability.sh --platform <ios|android> [--repo <path>]
#                                [--changed <f>[,<f>...]] [--json]
#                                [--json-out <path>] [--only all|device]
#
# Output: KEY='VALUE' lines (eval-able; every value is shell-quoted), or a JSON object with --json (shaped for
# state.evidenceCapability). --json-out writes that JSON to a file while stdout
# stays KEY=VALUE, so one run serves both the shell that builds the menu and the
# state that records the measurement - two runs would mean two repo scans and
# two chances to disagree.
#
# --only device skips the UI-test detection. Phase 3 re-checks the device right
# before recording, and nothing else it would re-scan can have changed.
#
# Exit: 0 probed (whatever the verdicts), 2 usage.
#       Never non-zero for an absent capability: absence is the finding.
set -uo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLATFORM=""; REPO="$PWD"; CHANGED=""; AS_JSON=0; JSON_OUT=""; ONLY="all"

while [ "$#" -gt 0 ]; do
  case "$1" in
    --platform) PLATFORM="${2:-}"; shift 2 ;;
    --repo) REPO="${2:-}"; shift 2 ;;
    --changed) CHANGED="${CHANGED:+$CHANGED,}${2:-}"; shift 2 ;;
    --json) AS_JSON=1; shift ;;
    --json-out) JSON_OUT="${2:-}"; shift 2 ;;
    --only) ONLY="${2:-all}"; shift 2 ;;
    *) echo "probe-evidence-capability: unknown option $1" >&2; exit 2 ;;
  esac
done
case "$ONLY" in all | device) ;; *)
  echo "probe-evidence-capability: --only takes 'all' or 'device'" >&2; exit 2 ;;
esac

case "$PLATFORM" in ios | android) ;; *)
  echo "usage: probe-evidence-capability.sh --platform <ios|android> [--repo <path>] [--changed <f>] [--json]" >&2
  exit 2 ;;
esac
[ -d "$REPO" ] || { echo "probe-evidence-capability: no such repo directory: $REPO" >&2; exit 2; }

UI_TEST_TARGET=""; UI_TEST_TARGETS=""; UI_TEST_TARGET_REASON=""
UI_TEST_MATCHES=""; UI_TEST_MATCH_REASON=""
DEVICE=""; DEVICE_REASON=""
RECORDER="unknown"; RECORDER_REASON=""
MCP="unknown"; MCP_REASON=""

# ---- UI test target, delegated ------------------------------------------------
RUNNER="$HERE/run-ui-tests.sh"
if [ "$ONLY" = "device" ]; then
  # Phase 3 re-checks the device right before recording, and only the device.
  # Delegating detection there would re-walk the whole repo for a row that
  # cannot have changed, which cost 5 seconds of a phase that is already holding
  # a green build.
  UI_TEST_TARGET_REASON="not probed (--only device)"
elif [ ! -x "$RUNNER" ] && [ ! -f "$RUNNER" ]; then
  UI_TEST_TARGET_REASON="run-ui-tests.sh not found beside this script"
else
  DETECT_ARGS=(detect --platform "$PLATFORM" --repo "$REPO")
  [ -n "$CHANGED" ] && DETECT_ARGS+=(--changed "$CHANGED")
  while IFS='=' read -r k v; do
    case "$k" in
      UI_TEST_TARGET) UI_TEST_TARGET="$v" ;;
      UI_TEST_TARGETS) UI_TEST_TARGETS="$v" ;;
      UI_TEST_TARGET_REASON) UI_TEST_TARGET_REASON="$v" ;;
      UI_TEST_MATCHES) UI_TEST_MATCHES="$v" ;;
      UI_TEST_MATCH_REASON) UI_TEST_MATCH_REASON="$v" ;;
    esac
  done < <(bash "$RUNNER" "${DETECT_ARGS[@]}" 2>/dev/null)
fi

# ---- Device -------------------------------------------------------------------
case "$PLATFORM" in
  ios)
    if ! command -v xcrun >/dev/null 2>&1; then
      DEVICE_REASON="xcrun unavailable; cannot tell whether a simulator exists"
    else
      DEVICE=$(xcrun simctl list devices booted 2>/dev/null | grep -oE '[0-9A-F-]{36}' | head -1)
      if [ -z "$DEVICE" ]; then
        # Bootable is not booted, and the difference is a question the user can
        # act on: one is "start your simulator", the other is "this machine has
        # no iOS runtime installed".
        if xcrun simctl list devices available 2>/dev/null | grep -q "([0-9A-F-]\{36\})"; then
          DEVICE_REASON="no booted simulator, but one is available to boot"
        else
          DEVICE_REASON="no iOS simulator available on this machine"
        fi
      fi
    fi
    ;;
  android)
    if ! command -v adb >/dev/null 2>&1; then
      DEVICE_REASON="adb unavailable; cannot tell whether a device is attached"
    else
      DEVICE=$(adb devices 2>/dev/null | awk 'NR>1 && $2=="device" {print $1; exit}')
      [ -n "$DEVICE" ] || DEVICE_REASON="no attached device or running emulator"
    fi
    ;;
esac

# ---- Recorder -----------------------------------------------------------------
# The recorder is the same CLI the capture needs, so this answers "would
# capture-evidence.sh video start work" rather than "is some recorder installed".
case "$PLATFORM" in
  ios)
    if command -v xcrun >/dev/null 2>&1; then RECORDER="true"; else
      RECORDER="false"; RECORDER_REASON="xcrun unavailable"
    fi
    ;;
  android)
    if command -v adb >/dev/null 2>&1; then RECORDER="true"; else
      RECORDER="false"; RECORDER_REASON="adb unavailable"
    fi
    ;;
esac
if [ "$RECORDER" = "true" ] && ! command -v ffprobe >/dev/null 2>&1; then
  # Not fatal. ffprobe only verifies the result, so its absence downgrades what
  # can be CHECKED about a recording, never whether one can be made.
  RECORDER_REASON="ffprobe unavailable; a recording cannot be verified after capture"
fi

# ---- Toolkit MCP --------------------------------------------------------------
# Registration only, never a handshake: a probe that spawns the server costs
# seconds at intake, and the question this answers is whether tier 2 may be
# offered at all.
CLAUDE_JSON="$HOME/.claude.json"
SETTINGS_JSON="$HOME/.claude/settings.json"
if command -v node >/dev/null 2>&1; then
  MCP=$(node -e '
    const fs = require("fs");
    const hit = (p) => {
      try {
        const j = JSON.parse(fs.readFileSync(p, "utf8"));
        return Object.keys(j.mcpServers || {}).some((k) => /toolkit/i.test(k));
      } catch { return false; }
    };
    process.stdout.write(process.argv.slice(1).some(hit) ? "true" : "false");
  ' "$CLAUDE_JSON" "$SETTINGS_JSON" 2>/dev/null)
  [ -n "$MCP" ] || MCP="unknown"
  [ "$MCP" = "false" ] && MCP_REASON="no mcpServers entry matching /toolkit/i"
  [ "$MCP" = "unknown" ] && MCP_REASON="could not read the MCP registration files"
else
  MCP="unknown"; MCP_REASON="node unavailable; cannot read the MCP registration"
fi

# ---- Tier verdicts ------------------------------------------------------------
# Computed before the report so both output forms carry them: a caller that
# persists the JSON and a caller that evals the KEY=VALUE lines must not have to
# re-derive the same booleans and risk deriving them differently.
# TIER1 needs a target AND a device AND a recorder; TIER2 drops the target and
# adds MCP; tier 3 is always reachable because "record nothing and say why" is.
T1=closed; T2=closed
[ -n "$UI_TEST_TARGET$UI_TEST_TARGETS" ] && [ -n "$DEVICE" ] && [ "$RECORDER" = "true" ] && T1=open
[ -n "$DEVICE" ] && [ "$RECORDER" = "true" ] && [ "$MCP" = "true" ] && T2=open

# --only device did not look for a target, so tier 1 is UNKNOWN here, not closed.
# Reporting it closed would be rule 2 broken by the file that states it: a caller
# re-checking the device before recording would read "tier 1 unavailable" from a
# measurement that never ran and downgrade a recording it could have made. Tier 2
# does not depend on the target, so it stays a real verdict.
[ "$ONLY" = "device" ] && T1=unknown

emit_json() {
  UI_TEST_TARGET="$UI_TEST_TARGET" UI_TEST_TARGETS="$UI_TEST_TARGETS" \
  UI_TEST_TARGET_REASON="$UI_TEST_TARGET_REASON" UI_TEST_MATCHES="$UI_TEST_MATCHES" \
  UI_TEST_MATCH_REASON="$UI_TEST_MATCH_REASON" DEVICE="$DEVICE" DEVICE_REASON="$DEVICE_REASON" \
  RECORDER="$RECORDER" RECORDER_REASON="$RECORDER_REASON" MCP="$MCP" MCP_REASON="$MCP_REASON" \
  PLATFORM="$PLATFORM" TIER1="$T1" TIER2="$T2" \
  node -e '
    const e = process.env;
    const list = (s) => (s ? s.split(",").filter(Boolean) : []);
    const tri = (s) => (s === "true" ? true : s === "false" ? false : null);
    process.stdout.write(JSON.stringify({
      platform: e.PLATFORM,
      uiTestTarget: e.UI_TEST_TARGET || null,
      uiTestTargets: list(e.UI_TEST_TARGETS),
      uiTestTargetReason: e.UI_TEST_TARGET_REASON || null,
      matchingTests: list(e.UI_TEST_MATCHES),
      matchingTestsReason: e.UI_TEST_MATCH_REASON || null,
      device: e.DEVICE || null,
      deviceReason: e.DEVICE_REASON || null,
      recorder: tri(e.RECORDER),
      recorderReason: e.RECORDER_REASON || null,
      mcp: tri(e.MCP),
      mcpReason: e.MCP_REASON || null,
      tier1: e.TIER1,
      tier2: e.TIER2,
    }, null, 2) + "\n");
  '
}

if [ -n "$JSON_OUT" ]; then
  mkdir -p "$(dirname "$JSON_OUT")" 2>/dev/null || true
  emit_json > "$JSON_OUT" || {
    echo "probe-evidence-capability: could not write $JSON_OUT" >&2
    exit 2
  }
fi

if [ "$AS_JSON" -eq 1 ]; then
  emit_json
  exit 0
fi

# Every value is single-quoted, because the caller EVALS this. The reasons are
# prose - "no booted simulator, but one is available to boot" - and an unquoted
# assignment makes eval run `booted` as a command and assign the first word. The
# bug is invisible on a machine where the reasons happen to be empty, which is
# exactly the machine a developer tests on.
q() { printf "%s='%s'" "$1" "$(printf '%s' "$2" | sed "s/'/'\\\\''/g")"; printf '\n'; }

q EVIDENCE_PLATFORM "$PLATFORM"
q EVIDENCE_UI_TEST_TARGET "$UI_TEST_TARGET"
q EVIDENCE_UI_TEST_TARGETS "$UI_TEST_TARGETS"
q EVIDENCE_UI_TEST_TARGET_REASON "$UI_TEST_TARGET_REASON"
q EVIDENCE_MATCHING_TESTS "$UI_TEST_MATCHES"
q EVIDENCE_MATCHING_TESTS_REASON "$UI_TEST_MATCH_REASON"
q EVIDENCE_DEVICE "$DEVICE"
q EVIDENCE_DEVICE_REASON "$DEVICE_REASON"
q EVIDENCE_RECORDER "$RECORDER"
q EVIDENCE_RECORDER_REASON "$RECORDER_REASON"
q EVIDENCE_MCP "$MCP"
q EVIDENCE_MCP_REASON "$MCP_REASON"

q EVIDENCE_TIER1 "$T1"
q EVIDENCE_TIER2 "$T2"
