#!/usr/bin/env bash
# model-rung.sh  -  read or flip the top model rung, and keep cost pricing with it.
#
# Backs /multi-agent:model. Two preference keys move as ONE write:
#   prefs.global.modelFallback.fableEnabled   the rung itself
#   prefs.global.costBudget.pricingModel      what the ledger prices against
#
# They were always meant to move together - model-fallback.md says so in prose -
# but nothing enforced it, so a hand edit that flipped one left the estimate
# pricing every call at a rung that was not in play. With the rung off and
# pricing still `fable`, the budget ceiling trips early and triggers a downgrade
# nothing needed. One write, or none.
#
# Usage:
#   model-rung.sh            report current state (exit 0)
#   model-rung.sh on         enable the fable rung
#   model-rung.sh off        disable it
#
# Exit codes:
#   0  -  reported or applied
#   2  -  bad argument
#   3  -  preferences file missing or unparseable (nothing written)

set -uo pipefail

PREFS="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}"

_MA_MR_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
if [ -f "$_MA_MR_HERE/user-facing.sh" ]; then . "$_MA_MR_HERE/user-facing.sh"; else . "$HOME/.claude/lib/user-facing.sh"; fi

ACTION="${1:-report}"

case "$ACTION" in
  report | on | off) ;;
  *)
    echo "usage: model-rung.sh [on|off]" >&2
    exit 2
    ;;
esac

# A missing `jq` must not look like a missing setting. Without this the reads
# below return empty, the command reports the rung as already off, and a user
# who asked to turn it off is told it was never on.
if ! command -v jq >/dev/null 2>&1; then
  echo "model-rung: jq not found  -  cannot read or write preferences (install: brew install jq)" >&2
  exit 3
fi

if [ ! -f "$PREFS" ]; then
  echo "model-rung: preferences not found at $PREFS  -  run $(ma_command setup) first" >&2
  exit 3
fi

if ! jq -e . "$PREFS" >/dev/null 2>&1; then
  echo "model-rung: $PREFS is not valid JSON  -  refusing to write over it" >&2
  exit 3
fi

CUR=$(jq -r '.global.modelFallback.fableEnabled // false' "$PREFS")
PRICING=$(jq -r '.global.costBudget.pricingModel // "fable"' "$PREFS")

host_note() {
  # Which host this is decides whether the switch is a switch or a status line.
  # Saying "enabled" on a host where the rung means something else entirely is
  # the surprise this block exists to avoid.
  case "${MULTI_AGENT_HOST:-claude}" in
    claude)
      echo "Claude Code: the fable rung is Fable 5. This switch is live here."
      ;;
    copilot)
      echo "Copilot CLI: Fable 5 is not offered; its personas never sat on this rung."
      echo "This setting is recorded but changes nothing on this host."
      ;;
    codex)
      echo "Codex CLI: the fable rung means gpt-5.6 @ xhigh, a different model on a"
      echo "different account. This switch deliberately does NOT touch it  -  a knob"
      echo "named after an Anthropic model must not silently retune a Codex run."
      ;;
    *)
      echo "Unknown host '${MULTI_AGENT_HOST}'; reporting the stored value only."
      ;;
  esac
}

if [ "$ACTION" = "report" ]; then
  echo "fable rung:    $CUR"
  echo "cost pricing:  $PRICING"
  if [ "$CUR" = "true" ] && [ "$PRICING" != "fable" ]; then
    echo "MISMATCH: rung is on but the ledger prices against '$PRICING'  -  run 'model on' to realign"
  elif [ "$CUR" != "true" ] && [ "$PRICING" = "fable" ]; then
    echo "MISMATCH: rung is off but the ledger still prices against fable  -  this trips"
    echo "          the budget ceiling early. Run 'model off' to realign."
  fi
  echo ""
  host_note
  exit 0
fi

if [ "$ACTION" = "on" ]; then
  WANT=true
  WANT_PRICING=fable
else
  WANT=false
  WANT_PRICING=opus
fi

TMP=$(mktemp) || { echo "model-rung: mktemp failed" >&2; exit 3; }
trap 'rm -f "$TMP"' EXIT

# Both keys in one filter. A two-step write can leave the pair half-applied if
# the second step fails, which is the exact state this command exists to prevent.
if ! jq --argjson want "$WANT" --arg pricing "$WANT_PRICING" '
      .global.modelFallback //= {}
    | .global.modelFallback.fableEnabled = $want
    | .global.costBudget //= {}
    | .global.costBudget.pricingModel = $pricing
  ' "$PREFS" > "$TMP"; then
  echo "model-rung: jq failed  -  preferences left untouched" >&2
  exit 3
fi

if ! jq -e . "$TMP" >/dev/null 2>&1; then
  echo "model-rung: produced invalid JSON  -  preferences left untouched" >&2
  exit 3
fi

mv "$TMP" "$PREFS"
trap - EXIT

# Agents spawned without an explicit model run on their frontmatter, so the
# installed fable-tier agents follow the switch in the same step.
if command -v node >/dev/null 2>&1 && [ -f "$_MA_MR_HERE/agent-model-sync.mjs" ]; then
  node "$_MA_MR_HERE/agent-model-sync.mjs" --prefs "$PREFS" ||
    echo "model-rung: installed agents could not be updated; run node $_MA_MR_HERE/agent-model-sync.mjs" >&2
fi

echo "fable rung:    $CUR -> $WANT"
echo "cost pricing:  $PRICING -> $WANT_PRICING"
echo ""

if [ "$WANT" = "false" ]; then
  # Said at the moment of the change rather than left in a doc: the panel
  # shrinking is the part people discover later and read as a bug.
  echo "Phase 3 reviewer panel: 3 models -> 2 (opus + sonnet)."
  echo "Reviewer 1 lands on opus, which Reviewer 2 already holds, and dispatching"
  echo "one model twice is not cross-model review. consensus.reviewerCount records 2."
  echo "Triage also runs on opus, so it shares a model with Reviewer 1  -  the Phase 3"
  echo "Step 3 anonymisation requirement is not optional while the rung is off."
  echo ""
fi

host_note
exit 0
