#!/usr/bin/env bash
# route-state.sh  -  read and write prefs.global.modelRouting.
#
# Backs /multi-agent:route-on, route-off and route-status. Ships disabled and
# stays that way until someone turns it on; while `enabled` is false nothing in
# this file changes one dispatch.
#
# The `off` contract is borrowed from autopilot-off and for the same reason: it
# clears the ON STATE, never the rules. Turning routing back on must not re-ask
# for a configuration the user already gave.
#
# Usage:
#   route-state.sh status                       print the current policy
#   route-state.sh on  [--strategy=S] [--scope=a,b]
#   route-state.sh off
#   route-state.sh set-rules <file.json>        replace rules[] from a JSON array
#
# Exit codes:
#   0  -  done
#   2  -  bad usage
#   3  -  preferences missing/unparseable, or a scope value this refuses

set -uo pipefail

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

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

# The scope values this accepts, spelled once. `host-session` is NOT here and
# must never be added: it would mean rewriting the host's base URL, which routes
# the user's whole session - work that has nothing to do with this pipeline
# included - through a third layer, breaks the subscription's auth model, and
# silently changes which model answers. The schema encodes the same list so a
# hand-edited preferences file is rejected too, rather than only this entry point.
ALLOWED_SCOPE="subagent bulk-read research"

die() { echo "route-state: $1" >&2; exit "${2:-3}"; }

# A missing `jq` must not look like routing being off. Every read below would
# come back empty, `route-status` would report disabled, and a user with rules
# armed would be told there are none.
command -v jq >/dev/null 2>&1 || die "jq not found  -  cannot read or write preferences (install: brew install jq)"
[ -f "$PREFS" ] || die "preferences not found at $PREFS  -  run $(ma_command setup) first"
jq -e . "$PREFS" >/dev/null 2>&1 || die "$PREFS is not valid JSON  -  refusing to write over it"

ACTION="${1:-status}"; shift 2>/dev/null || true

write_prefs() {
  local filter="$1"; shift
  local tmp
  tmp=$(mktemp) || die "mktemp failed"
  if ! jq "$@" "$filter" "$PREFS" > "$tmp"; then rm -f "$tmp"; die "jq failed  -  preferences left untouched"; fi
  jq -e . "$tmp" >/dev/null 2>&1 || { rm -f "$tmp"; die "produced invalid JSON  -  preferences left untouched"; }
  mv "$tmp" "$PREFS"
}

case "$ACTION" in
  status)
    ENABLED=$(jq -r '.global.modelRouting.enabled // false' "$PREFS")
    STRATEGY=$(jq -r '.global.modelRouting.strategy // "manual"' "$PREFS")
    SCOPE=$(jq -r '(.global.modelRouting.scope // ["subagent"]) | join(", ")' "$PREFS")
    NRULES=$(jq -r '(.global.modelRouting.rules // []) | length' "$PREFS")
    CEIL=$(jq -r '.global.modelRouting.budgetCeilingUsd // "none"' "$PREFS")
    REC=$(jq -r '.global.modelRouting.recordDecisions // true' "$PREFS")

    echo "routing:   $ENABLED"
    echo "strategy:  $STRATEGY"
    echo "scope:     $SCOPE"
    echo "rules:     $NRULES"
    echo "ceiling:   $CEIL"
    echo "decisions: $REC"
    echo ""

    if [ "$ENABLED" != "true" ]; then
      echo "Disabled. Rules are stored but read by nothing; dispatch is unchanged."
      [ "$NRULES" -gt 0 ] && echo "route-on turns these $NRULES rule(s) back on without re-asking."
      exit 0
    fi

    if [ "$NRULES" -eq 0 ]; then
      echo "Armed with no rules. Nothing matches, so nothing is routed  -  this is a"
      echo "configuration state, not an error."
    else
      jq -r '(.global.modelRouting.rules // [])[] |
        "  when " + ([.when | to_entries[] | "\(.key)=\(.value)"] | join(" ")) +
        "  ->  " + (.prefer | join(" > "))' "$PREFS"
    fi

    echo ""
    # The limit is printed every time rather than documented once, because the
    # question it answers ("routing is on, why is the reviewer still on Opus")
    # otherwise arrives days later as a bug report.
    echo "Honest limit: on Claude Code a subagent cannot be sent to a non-Anthropic"
    echo "model  -  subagent dispatch belongs to the host. Phase 1/2/3 personas stay"
    echo "inside the Anthropic ladder whatever the rules say. External providers apply"
    echo "only where this pipeline makes the call itself (bulk-read, research)."
    ;;

  on)
    STRATEGY="manual"; SCOPE_ARG=""
    for arg in "$@"; do
      case "$arg" in
        --strategy=*) STRATEGY="${arg#*=}" ;;
        --scope=*)    SCOPE_ARG="${arg#*=}" ;;
        *) die "unknown option: $arg" 2 ;;
      esac
    done
    case "$STRATEGY" in
      manual|task-fit|cost-ceiling) ;;
      *) die "strategy must be manual, task-fit or cost-ceiling (got '$STRATEGY')" 2 ;;
    esac

    if [ -n "$SCOPE_ARG" ]; then
      SCOPE_JSON="[]"
      IFS=',' read -r -a parts <<< "$SCOPE_ARG"
      for one in "${parts[@]}"; do
        one="${one// /}"
        # shellcheck disable=SC2076
        case " $ALLOWED_SCOPE " in
          *" $one "*) ;;
          *) die "scope '$one' is not allowed (allowed: $ALLOWED_SCOPE). 'host-session' is refused by design: it would route the whole session, not this pipeline's calls." ;;
        esac
        SCOPE_JSON=$(jq -c --arg s "$one" '. + [$s]' <<< "$SCOPE_JSON")
      done
      write_prefs '
          .global.modelRouting //= {}
        | .global.modelRouting.enabled = true
        | .global.modelRouting.strategy = $st
        | .global.modelRouting.scope = ($sc | fromjson)
      ' --arg st "$STRATEGY" --arg sc "$SCOPE_JSON"
    else
      write_prefs '
          .global.modelRouting //= {}
        | .global.modelRouting.enabled = true
        | .global.modelRouting.strategy = $st
        | .global.modelRouting.scope //= ["subagent"]
      ' --arg st "$STRATEGY"
    fi
    echo "routing enabled (strategy: $STRATEGY)"
    exec "$0" status
    ;;

  off)
    # Rules survive on purpose. Clearing them here would make route-off a
    # destructive action wearing the name of a toggle.
    write_prefs '.global.modelRouting //= {} | .global.modelRouting.enabled = false'
    echo "routing disabled. Rules kept  -  route-on restores this configuration as it is."
    ;;

  set-rules)
    FILE="${1:-}"
    [ -n "$FILE" ] || die "set-rules needs a JSON file containing an array of rules" 2
    [ -f "$FILE" ] || die "no such file: $FILE" 2
    jq -e 'type == "array"' "$FILE" >/dev/null 2>&1 || die "$FILE must contain a JSON ARRAY of rules" 2
    write_prefs '.global.modelRouting //= {} | .global.modelRouting.rules = $r' --slurpfile _ignore /dev/null --argjson r "$(cat "$FILE")"
    echo "rules replaced: $(jq -r 'length' "$FILE")"
    ;;

  *)
    echo "usage: route-state.sh [status|on|off|set-rules <file>]" >&2
    exit 2
    ;;
esac
