#!/usr/bin/env bash
# claude-mode - switch Claude Code system-wide between Anthropic and the gateway
# providers in providers.json (OpenRouter, Z.AI, LM Studio, Ollama, custom
# endpoints), with named model presets.
#
# POSIX port of the Windows/PowerShell build. Same design: the switch rewrites
# the managed keys inside ~/.claude/settings.json, which Claude Code re-reads at
# every startup, so it applies to every new `claude` invocation - CLI, VS Code
# extension, desktop app - with nothing to re-source.
#
# This file holds the settings, loads the rest from lib/, and dispatches the
# command line; docs/architecture.md says which lib file does what. Secrets
# never enter settings.json; see cm-vault.sh for the storage backends.

set -uo pipefail

# Everything this script loads sits beside it: linux/ in a checkout,
# ~/.claude-mode/bin when installed. So a checkout runs its own code rather than
# the installed version's. ~/.local/bin/claude-mode is a symlink, hence the
# loop - macOS readlink has no -f.
cm_self="${BASH_SOURCE[0]}"
while [ -L "$cm_self" ]; do
    cm_link="$(readlink "$cm_self")"
    case "$cm_link" in
        /*) cm_self="$cm_link" ;;
        *)  cm_self="$(dirname "$cm_self")/$cm_link" ;;
    esac
done
CM_HERE="$(cd "$(dirname "$cm_self")" && pwd)"
unset cm_self cm_link

CM_ROOT="${CM_ROOT:-$HOME/.claude-mode}"
CM_BIN="$CM_ROOT/bin"
CM_PRESETS="$CM_ROOT/presets"
CM_BACKUPS="$CM_ROOT/backups"
CM_STATE="$CM_ROOT/state.json"
CM_IGNORED="$CM_ROOT/ignored-sessions.json"
CM_MODELS_CACHE="$CM_ROOT/models-cache.json"
CM_DEFAULTS="$CM_ROOT/defaults.json"
CM_SETTINGS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
CM_SETTINGS="$CM_SETTINGS_DIR/settings.json"
CM_HELPER="$CM_BIN/claude-key-helper.sh"   # the installed one: settings.json points here

PY="${CLAUDE_MODE_PYTHON:-python3}"
JSON="$CM_HERE/cm-json.py"

# shellcheck source=/dev/null
. "$CM_HERE/cm-vault.sh"

CM_FORCE=0
CM_SAME_ENDPOINT=0   # set by reapply_if_active for a tier-only edit

# anthropic is built in; every gateway provider is appended from providers.json
# at startup (see cm_providers_load).
MODES=(anthropic)
TIERS=(opus sonnet haiku fable)

# The rest of the CLI, one file per concern, loaded in the order it was written:
# a few files assign globals as they load (the colour palette, the provider
# table's placeholder, session flags), and every function exists before the
# dispatch below runs.
for cm_lib in output core preflight sessions switch catalogue commands doctor presets menu setup repair; do
    if [ ! -f "$CM_HERE/lib/$cm_lib.sh" ]; then
        echo "claude-mode: $CM_HERE/lib/$cm_lib.sh is missing - re-run the installer" >&2
        exit 1
    fi
    # shellcheck source=/dev/null
    . "$CM_HERE/lib/$cm_lib.sh"
done
unset cm_lib

# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------

command -v "$PY" >/dev/null 2>&1 || { echo "claude-mode: $PY not found (set CLAUDE_MODE_PYTHON)" >&2; exit 1; }
init_root
cm_providers_load

# --force is global and may appear anywhere; strip it before the mode arguments
# are positional-matched below.
_args=()
for _a in "$@"; do
    case "$_a" in
        --force)   CM_FORCE=1 ;;
        -y|--yes)  CM_ASSUME_YES=1 ;;
        *)       _args+=("$_a") ;;
    esac
done
set -- ${_args[@]+"${_args[@]}"}

cmd="${1:-}"; [ $# -gt 0 ] && shift
case "$cmd" in
    ''|menu)     ui_menu ;;
    status)      cmd_status ;;
    anthropic)   set_mode anthropic ;;
    presets)     cmd_presets ;;
    preset)      cmd_preset "$@" ;;
    set-key)
                 # --terminal is for callers with no stdin to offer: the bar
                 # widget cannot host a hidden password prompt, so it asks the
                 # terminal to host one instead.
                 # `set-key [ref] [key]`. The key is normally typed at the
                 # hidden prompt; a second word is taken as the key itself, for
                 # scripting. Anything key-shaped in the *ref* position is also
                 # taken as the key for the default ref - it used to become the
                 # ref name instead, which stored the wrong thing under a ref
                 # nobody would ever look up, and only failed later and quietly.
                 _ref=''; _term=0; _secret=''
                 for _a in "$@"; do
                     case "$_a" in
                         --terminal) _term=1 ;;
                         *)
                             if [ -z "$_ref" ]; then
                                 case "$_a" in
                                     sk-*|sk_*) _secret="$_a" ;;
                                     *) if [ "${#_a}" -gt 24 ]; then _secret="$_a"; else _ref="$_a"; fi ;;
                                 esac
                             elif [ -z "$_secret" ]; then
                                 _secret="$_a"
                             else
                                 err "unexpected extra argument '$_a'"; exit 1
                             fi ;;
                     esac
                 done
                 [ -n "$_ref" ] || _ref=openrouter
                 if [ "$_term" -eq 1 ]; then
                     _t="$(cm_terminal_cmd)"
                     setsid nohup "$_t" -e bash -lc \
                        "'$0' set-key '$_ref'; printf '\n  press enter to close '; read -r _" \
                        >/dev/null 2>&1 &
                     ok "opened $_t to store the '$_ref' key"
                 else
                     cmd_set_key "$_ref" "$_secret"
                 fi ;;
    models)      cmd_models "$@" ;;
    doctor)      cmd_doctor ;;
    health)      write_health "$(state_mode)" "$(state_preset)" ;;
    preflight)   cmd_preflight "${1:-}" "${2:-}" ;;
    setup)
                 # --terminal for callers with no stdin to offer. The bar widget
                 # cannot host a hidden key prompt or a filter-select list, so it
                 # asks a terminal to host the whole flow instead.
                 _mode="${1:-}"; _term=0; _rest=''
                 for _a in "$@"; do
                     case "$_a" in
                         --terminal) _term=1 ;;
                         "$_mode")   ;;
                         *)          _rest="$_a" ;;
                     esac
                 done
                 if [ "$_term" -eq 1 ]; then
                     _t="$(cm_terminal_cmd)"
                     setsid nohup "$_t" -e bash -lc \
                        "'$0' setup '$_mode' $_rest; printf '\n  press enter to close '; read -r _" \
                        >/dev/null 2>&1 &
                     ok "opened $_t to set up $_mode"
                 else
                     cmd_setup "$_mode" "$_rest"
                 fi ;;
    sessions)    cmd_sessions "$@" ;;
    repair-session) cmd_repair_session "$@" ;;
    repair)
                 scope=''
                 for a in "$@"; do [ "$a" = "--all" ] && scope=all; done
                 cmd_repair "$scope" ;;
    help|--help|-h) usage ;;
    *)
                 # Any provider in providers.json, by id or alias, is a mode.
                 # Last, so a provider can never shadow a real command.
                 if _prov="$(provider_resolve "$cmd")"; then
                     p="$(resolve_preset "$_prov" "${1:-}")" || exit 1
                     set_mode "$_prov" "$p"
                 else
                     err "unknown command '$cmd'"; usage; exit 1
                 fi ;;
esac
