#!/usr/bin/env bash
# ==============================================================================
# avoid - Coding Agent Configuration Manager (MrChromebox-Inspired TUI)
# Unified terminal control plane for Claude Code & OpenAI Codex CLI
# ==============================================================================
set -euo pipefail

# ------------------------------------------------------------------------------
# 1. Colors, Palettes & Typography (MrChromebox Aesthetic)
# ------------------------------------------------------------------------------
if [[ -t 1 && -z "${NO_COLOR:-}" && "${TERM:-dumb}" != "dumb" ]]; then
  NORMAL=$'\033[0m'
  BOLD=$'\033[1m'
  DIM=$'\033[2m'
  MENU=$'\033[1;34m'       # Blue borders
  NUMBER=$'\033[1;33m'     # Yellow menu keys
  GREEN=$'\033[1;32m'      # Success
  RED=$'\033[1;31m'        # Errors
  GRAY=$'\033[39m'        # Terminal foreground, readable on light and dark themes
  CYAN=$'\033[1;36m'       # Highlights
  ENTER_LINE=$'\033[1;33m'
else
  NORMAL=""
  BOLD=""
  DIM=""
  MENU=""
  NUMBER=""
  GREEN=""
  RED=""
  GRAY=""
  CYAN=""
  ENTER_LINE=""
fi

CONFIG_DIR="${AVOID_CONFIG_DIR:-${HOME}/.config/avoid}"
CONFIG_FILE="${CONFIG_DIR}/config.json"
EMPTY_STORE='{"providers":[]}'
LOCK_DIR="${CONFIG_DIR}/.lock"
CLAUDE_CONFIG_FILE="${HOME}/.claude/settings.json"
CODEX_CONFIG_FILE="${HOME}/.codex/config.toml"

STAGING_DIR=""
STAGING_FILE=""
HELD_LOCK_DIR=""
SAVED_INPUT_MODE=""
UI_WIDTH=79

report_error() {
  if [[ -t 2 ]]; then
    ui_text "$1" "$RED" >&2
  else
    printf '%s%s%s\n' "$RED" "$1" "$NORMAL" >&2
  fi
}

check_dependencies() {
  local req missing="" install
  for req in curl jq fzf; do
    if ! command -v "$req" >/dev/null 2>&1; then
      missing="${missing:+$missing }$req"
    fi
  done
  [[ -z "$missing" ]] && return 0

  if [[ "$OSTYPE" == darwin* ]]; then
    install="Run: brew install $missing"
  else
    install="Install with your package manager: $missing"
  fi
  printf '%savoid%s: missing required %s%s%s. %s\n' \
    "$NUMBER" "$NORMAL" "$BOLD" "$missing" "$NORMAL" "$install" >&2
  return 1
}

# ------------------------------------------------------------------------------
# 2. Storage, Concurrency Lock & Atomic Swap
# ------------------------------------------------------------------------------
ensure_store() {
  [[ -d "$CONFIG_DIR" ]] || { mkdir -p "$CONFIG_DIR" && chmod 700 "$CONFIG_DIR"; }
  CONFIG_DIR="$(cd "$CONFIG_DIR" && pwd -P)"
  CONFIG_FILE="${CONFIG_DIR}/config.json"
  LOCK_DIR="${CONFIG_DIR}/.lock"
  if [[ ! -f "$CONFIG_FILE" ]]; then
    acquire_lock
    [[ -f "$CONFIG_FILE" ]] || atomic_deploy "$CONFIG_FILE" "$EMPTY_STORE"
    release_lock
  fi
}

read_store() {
  local status
  if jq -ces '
    if length == 1 and (.[0] | type == "object" and (.providers | type == "array")
      and all(.providers[]; type == "object" and (.name | type == "string" and length > 0)))
    then .[0] else error("expected one provider store") end
  ' "$CONFIG_FILE" 2>/dev/null; then
    return 0
  else
    status=$?
    report_error "Provider store is unreadable or invalid (jq exit $status). Use Clear Configuration Files in Provider Management to reset it."
    return 1
  fi
}

read_provider() {
  local store status
  store="$(read_store)" || return
  if printf '%s\n' "$store" | jq -ce --arg n "$1" '
    .providers | map(select(.name == $n))
    | if length == 1 then .[0] else error("expected one provider") end
    | if (.baseUrl | type == "string" and test("^https?://.+"))
      and (.apiKey | type == "string" and test("\\S"))
      and (.modelsUrl == null or .modelsUrl == ""
        or (.modelsUrl | type == "string" and test("^https?://.+")))
      then . else error("invalid provider fields") end
  ' 2>/dev/null; then
    return 0
  else
    status=$?
    report_error "Selected provider is missing, duplicated, or invalid (jq exit $status)."
    return 1
  fi
}

acquire_lock() {
  local count=0
  while ! mkdir "$LOCK_DIR" 2>/dev/null; do
    if [[ ! -d "$LOCK_DIR" ]]; then
      report_error "Cannot create the provider lock: $LOCK_DIR"
      return 1
    fi
    sleep 0.05
    count=$((count + 1))
    [[ $count -ge 60 ]] && { report_error "Provider store is busy (lock timeout)."; return 1; }
  done
  HELD_LOCK_DIR="$LOCK_DIR"
}

# Only paths registered when this process creates them are passed here.
remove_owned_path() {
  local target="$1" kind="$2" parent
  if [[ "$target" != /* || "$target" == / ]]; then
    report_error "Invalid cleanup path: $target"
    return 1
  fi
  [[ -e "$target" || -L "$target" ]] || return 0
  parent="$(cd "$(dirname "$target")" && pwd -P)" || return
  if [[ "$target" != "$parent/$(basename "$target")" || -L "$target" || ! -O "$target" ]]; then
    report_error "Cannot verify cleanup ownership: $target"
    return 1
  fi
  if [[ "$kind" == file ]]; then
    rm -- "$target"
  else
    rmdir -- "$target"
  fi
}

release_lock() {
  local held="$HELD_LOCK_DIR"
  HELD_LOCK_DIR=""
  [[ -z "$held" ]] || remove_owned_path "$held" directory
}

cleanup_staging() {
  local file="$STAGING_FILE" directory="$STAGING_DIR"
  STAGING_FILE=""
  STAGING_DIR=""
  [[ -z "$file" ]] || remove_owned_path "$file" file || return
  [[ -z "$directory" ]] || remove_owned_path "$directory" directory
}

cleanup() {
  local status=$?
  trap - EXIT
  restore_input_mode || status=1
  cleanup_staging || status=1
  release_lock || status=1
  return "$status"
}

replacement_directory() {
  local target="$1"
  if [[ "$OSTYPE" == darwin* ]]; then
    /usr/bin/osascript -l JavaScript -e '
      ObjC.import("Foundation");
      function run(args) {
        var error = Ref();
        var directory = $.NSFileManager.defaultManager
          .URLForDirectoryInDomainAppropriateForURLCreateError(
            $.NSItemReplacementDirectory, $.NSUserDomainMask,
            $.NSURL.fileURLWithPath(args[0]), true, error);
        if (!directory) throw Error(ObjC.unwrap(error[0].localizedDescription));
        return ObjC.unwrap(directory.path);
      }
    ' "$target"
  else
    mktemp -d "$(dirname "$target")/.avoid.XXXXXXXX"
  fi
}

atomic_deploy() {
  local target_file="$1" content="$2"
  local parent_dir resolved_staging
  parent_dir="$(dirname "$target_file")"
  mkdir -p "$parent_dir" || return
  chmod 700 "$parent_dir" || return
  parent_dir="$(cd "$parent_dir" && pwd -P)" || return
  target_file="$parent_dir/$(basename "$target_file")"
  if [[ -d "$target_file" ]]; then
    report_error "Configuration destination is a directory: $target_file"
    return 1
  fi

  STAGING_DIR="$(replacement_directory "$target_file")" || return
  resolved_staging="$(cd "$STAGING_DIR" && pwd -P)" || return
  STAGING_DIR="$resolved_staging"
  chmod 700 "$STAGING_DIR" || return
  STAGING_FILE="$(mktemp "$STAGING_DIR/config.XXXXXXXX")" || return
  chmod 600 "$STAGING_FILE" || return

  printf '%s\n' "$content" > "$STAGING_FILE" || return
  mv -f -- "$STAGING_FILE" "$target_file" || return
  STAGING_FILE=""
  cleanup_staging
}

reset_store_target() {
  local target="$CONFIG_FILE" parent
  [[ -n "$target" && "$target" != / ]] || { report_error "Invalid configuration path."; return 1; }
  [[ "$target" == /* ]] || target="$PWD/$target"
  parent="$(dirname "$target")"
  if [[ -d "$parent" ]]; then
    parent="$(cd "$parent" && pwd -P)" || return
    target="$parent/$(basename "$target")"
  elif [[ -e "$parent" || -L "$parent" ]]; then
    report_error "Configuration parent is not a directory: $parent"
    return 1
  fi
  if [[ -L "$target" || ( -e "$target" && ( ! -f "$target" || ! -O "$target" ) ) ]]; then
    report_error "Refusing to reset a directory, symlink, or unowned file: $target"
    return 1
  fi
  printf '%s\n' "$target"
}

reset_store() {
  local target parent status=0
  target="$(reset_store_target)" || return
  parent="$(dirname "$target")"
  [[ -d "$parent" ]] || return 0
  CONFIG_DIR="$parent"
  CONFIG_FILE="$target"
  LOCK_DIR="$CONFIG_DIR/.lock"
  acquire_lock || return

  # Recheck under the lock without reading or initializing the old store.
  if target="$(reset_store_target)"; then
    if [[ -e "$target" ]]; then
      atomic_deploy "$target" "$EMPTY_STORE" || {
        status=$?
        report_error "Could not reset avoid configuration (exit $status)."
      }
    fi
  else
    status=$?
  fi
  cleanup_staging || status=$?
  release_lock || status=$?
  return "$status"
}

# ------------------------------------------------------------------------------
# 4. Model Discovery & Upstream Handshake
# ------------------------------------------------------------------------------
# Base URL is consumed only by the selected agent template. Models URL is
# consumed only by this model discovery request; neither address is inferred.
fetch_models() {
  local models_url="$1" api_key="$2"
  if [[ -z "$models_url" ]]; then
    report_error "Models URL is required for model discovery."
    return 1
  fi

  local response status body parsed
  if response="$(curl -sS -m 10 -w $'\n%{http_code}' \
    -H "Authorization: Bearer ${api_key}" \
    -H "x-api-key: ${api_key}" \
    --url "$models_url" 2>/dev/null)"; then
    :
  else
    status=$?
    if [[ $status -eq 28 ]]; then
      report_error "Model request timed out after 10 seconds (curl exit 28)."
    else
      report_error "Could not reach the model endpoint (curl exit $status)."
    fi
    return 1
  fi

  status="${response##*$'\n'}"
  body="${response%$'\n'*}"
  case "$status" in
    2[0-9][0-9]) ;;
    401|403) report_error "Model request denied (HTTP $status). Check the API key and access permissions."; return 1 ;;
    404) report_error "Model endpoint not found (HTTP 404). Check the Models URL."; return 1 ;;
    429) report_error "Model request rate-limited (HTTP 429). Try again later."; return 1 ;;
    *) report_error "Model endpoint returned HTTP $status."; return 1 ;;
  esac

  if body="$(printf '%s' "$body" | jq -cs '
    if length == 1 then .[0] else error("expected one JSON response") end
  ' 2>/dev/null)"; then
    :
  else
    status=$?
    report_error "Model endpoint returned invalid JSON (jq exit $status)."
    return 1
  fi

  if parsed="$(printf '%s\n' "$body" | jq -r '
    (if type == "array" then .
     elif type == "object" and (.data | type) == "array" then .data
     else error("expected a model array") end)
    | map(if type == "object" then .id else . end)
    | if all(.[]; type == "string" and (test("[\u0000-\u001f\u007f]") | not))
      then . else error("expected single-line string IDs") end
    | map(select(length > 0)) | unique | .[]
  ' 2>/dev/null)"; then
    :
  else
    status=$?
    report_error "Model endpoint returned an unsupported model list (jq exit $status; expected an array or data array of model IDs)."
    return 1
  fi

  [[ -z "$parsed" ]] || printf '%s\n' "$parsed"
}

trim_whitespace() {
  local value="$1"
  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

is_http_url() {
  case "$1" in
    http://?*|https://?*) return 0 ;;
    *) return 1 ;;
  esac
}

# ------------------------------------------------------------------------------
# 5. Agent Template Applicators
# ------------------------------------------------------------------------------
apply_claude() {
  local provider_name="$1" model="$2" base_url="$3" api_key="$4"
  local target="$CLAUDE_CONFIG_FILE"
  local json
  json="$(jq -n \
    --arg m "$model" --arg b "$base_url" --arg k "$api_key" '
  {
    "$schema": "https://json.schemastore.org/claude-code-settings.json",
    "cleanupPeriodDays": 36500,
    "theme": "light",
    "model": $m,
    "availableModels": [$m],
    "env": {
      "ANTHROPIC_BASE_URL": $b,
      "ANTHROPIC_AUTH_TOKEN": $k,
      "ANTHROPIC_MODEL": $m,
      "ANTHROPIC_DEFAULT_MODEL": $m,
      "CLAUDE_CODE_SUBAGENT_MODEL": $m,
      "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
      "CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
      "ENABLE_TOOL_SEARCH": "true"
    },
    "permissions": { "defaultMode": "bypassPermissions", "deny": ["WebSearch", "WebFetch"] },
    "skipDangerousModePermissionPrompt": true
  }')"
  atomic_deploy "$target" "$json"
}

apply_codex() {
  local provider_name="$1" model="$2" base_url="$3" api_key="$4"
  local target="$CODEX_CONFIG_FILE"
  local safe_b safe_k safe_m
  safe_b="$(toml_string "$base_url")"
  safe_k="$(toml_string "$api_key")"
  safe_m="$(toml_string "$model")"
  local toml
  toml="$(cat <<EOF
#:schema https://developers.openai.com/codex/config-schema.json
model = ${safe_m}
model_provider = "avoid"
cli_auth_credentials_store = "file"
service_tier = "default"

model_reasoning_summary = "detailed"
model_verbosity = "high"

approval_policy = "never"
sandbox_mode = "danger-full-access"

check_for_update_on_startup = false
suppress_unstable_features_warning = true
background_terminal_max_timeout = 3600000

[model_providers.avoid]
name = "OpenAI"
base_url = ${safe_b}
experimental_bearer_token = ${safe_k}
wire_api = "responses"
supports_websockets = false
request_max_retries = 100
stream_max_retries = 100
stream_idle_timeout_ms = 600000

[agents]
enabled = true
max_concurrent_threads_per_session = 64

[history]
persistence = "save-all"

[tools.web_search]
context_size = "high"
EOF
)"
  atomic_deploy "$target" "$toml"
}

toml_string() {
  # JSON escaping also covers TOML basic strings, with DEL escaped explicitly.
  jq -nr --arg value "$1" '$value | @json | gsub("\u007f"; "\\u007f")'
}

# ------------------------------------------------------------------------------
# 6. UI Banner & Layout Functions (MrChromebox Centered Box Design)
# ------------------------------------------------------------------------------
clear_screen() {
  [[ ! -t 1 || "${TERM:-dumb}" == dumb ]] || printf '\033[2J\033[H'
}

quit_to_shell() {
  clear_screen
  exit 0
}

show_banner() {
  local title="${1:-avoid - Coding Agent Configuration Manager}"
  local size columns=80 line inner_width pad_left pad_right
  if [[ -t 1 ]] && size="$(stty size 2>/dev/null)"; then
    columns="${size##* }"
  fi
  case "$columns" in ''|*[!0-9]*) columns=80 ;; esac
  [[ "$columns" -gt 8 ]] || columns=80
  UI_WIDTH=$((columns - 1))
  [[ "$UI_WIDTH" -le 79 ]] || UI_WIDTH=79
  clear_screen
  show_divider
  inner_width=$((UI_WIDTH - 4))
  while IFS= read -r line; do
    pad_left=$(( (inner_width - ${#line}) / 2 ))
    pad_right=$((inner_width - ${#line} - pad_left))
    printf '%s**%s%*s%s%s%s%*s%s**%s\n' "$MENU" "$NORMAL" \
      "$pad_left" '' "$BOLD" "$line" "$NORMAL" "$pad_right" '' "$MENU" "$NORMAL"
  done < <(printf '%s\n' "$title" | fold -s -w "$inner_width")
  show_divider
  printf '\n'
}

show_divider() {
  local line
  printf -v line '%*s' "$UI_WIDTH" ''
  printf '%s%s%s\n' "$MENU" "${line// /*}" "$NORMAL"
}

ui_text() {
  local text="$1" color="${2:-}" indent="${3:-}" line
  if [[ ! -t 1 ]]; then
    printf '%s%s%s%s\n' "$indent" "$color" "$text" "$NORMAL"
    return 0
  fi
  local width=$((UI_WIDTH - ${#indent}))
  while IFS= read -r line; do
    printf '%s%s%s%s\n' "$indent" "$color" "$line" "$NORMAL"
  done < <(printf '%s\n' "$text" | fold -s -w "$width")
}

ui_option() {
  local key="$1" label="$2" color="${3:-}" line prefix
  if [[ ! -t 1 ]]; then
    printf '  [%s%s%s] %s%s%s\n' "$NUMBER" "$key" "$NORMAL" "$color" "$label" "$NORMAL"
    return 0
  fi
  printf -v prefix '  [%s] ' "$key"
  while IFS= read -r line; do
    if [[ -n "$key" ]]; then
      printf '  [%s%s%s] %s%s%s\n' "$NUMBER" "$key" "$NORMAL" "$color" "$line" "$NORMAL"
      key=''
    else
      printf '%*s%s%s%s\n' "${#prefix}" '' "$color" "$line" "$NORMAL"
    fi
  done < <(printf '%s\n' "$label" | fold -s -w "$((UI_WIDTH - ${#prefix}))")
}

show_navigation() {
  local parent="${1:-}" mode="${2:-menu}"
  printf '\n'
  show_divider
  if [[ "$mode" == list || "$mode" == form ]]; then
    [[ "$mode" != list ]] || ui_option Ctrl-G 'Enter Model ID Manually'
    ui_option Esc "Back to $parent"
    ui_option Ctrl-Q 'Quit to Shell'
  else
    if [[ -n "$parent" ]]; then
      if [[ "$mode" == result ]]; then
        ui_option Enter/B "Back to $parent"
      else
        ui_option B "Back to $parent"
      fi
    fi
    ui_option Q 'Quit to Shell'
  fi
  printf '\n'
}

pause_key() {
  local parent="${1:-Previous Menu}" choice
  show_navigation "$parent" result
  while true; do
    read_menu_choice 'Enter choice: '
    case "$choice" in
      ""|[bB]) return 0 ;;
      [qQ]) quit_to_shell ;;
      *) report_error "Invalid selection." ;;
    esac
  done
}

read_menu_choice() {
  read -e -r -p "$1" choice || quit_to_shell
  choice="$(trim_whitespace "$choice")"
}

restore_input_mode() {
  [[ -n "$SAVED_INPUT_MODE" ]] || return 0
  stty "$SAVED_INPUT_MODE" || return
  SAVED_INPUT_MODE=""
}

read_form_value() {
  local prompt="$1" secret="${2:-0}" initial="${3:-}" command input_status=0 output key color=--no-color
  FORM_VALUE=""
  FORM_ACTION="value"
  if [[ "$secret" == 1 ]]; then
    # Bash 3.2 prints the prompt before read -s disables terminal echo.
    if [[ -t 0 ]]; then
      SAVED_INPUT_MODE="$(stty -g)" || return
      stty -echo || return
    fi
    read -r -s -p "$prompt" FORM_VALUE || input_status=$?
    restore_input_mode || return
    echo ""
    [[ "$input_status" -eq 0 ]] || quit_to_shell
  elif [[ -t 0 && -t 1 && "${TERM:-dumb}" != dumb ]]; then
    [[ -z "$NORMAL" ]] || color='--color=prompt:yellow,query:-1'
    # fzf supplies an editable initial value on Bash 3.2 as well as newer shells.
    output="$(FZF_DEFAULT_OPTS= FZF_DEFAULT_OPTS_FILE= fzf --height=1 \
      --layout=reverse --no-info --no-separator --border=none --no-bold "$color" \
      --prompt="$prompt" --query="$initial" --print-query \
      --expect=enter,esc,ctrl-q,ctrl-c </dev/null)" || input_status=$?
    case "$input_status" in
      0|1) ;;
      130) quit_to_shell ;;
      *) report_error "Field editor failed (fzf exit $input_status)."; exit "$input_status" ;;
    esac
    FORM_VALUE="${output%%$'\n'*}"
    key="${output#*$'\n'}"
    case "$key" in
      esc) FORM_ACTION=back; return 0 ;;
      ctrl-q) quit_to_shell ;;
      ctrl-c) exit 130 ;;
      enter) ;;
      *) quit_to_shell ;;
    esac
  else
    if [[ -t 0 && -n "$initial" ]]; then
      ui_text "$initial"
      history -s -- "$initial"
    fi
    read -e -r -p "$prompt" FORM_VALUE || quit_to_shell
  fi
  command="$(trim_whitespace "$FORM_VALUE")"
  case "$command" in
    [bB]) FORM_ACTION="back" ;;
    [qQ]) quit_to_shell ;;
  esac
}

read_list_choice() {
  local output key color="--color=header:-1"
  LIST_ACTION="back"
  LIST_VALUE=""
  LIST_STATUS=0
  [[ -n "$NORMAL" ]] || color="--no-color"
  if output="$(FZF_DEFAULT_OPTS= FZF_DEFAULT_OPTS_FILE= fzf --height=40% --min-height=3 --layout=reverse --expect=ctrl-q,ctrl-c \
    --bind=esc:abort "$color" "$@")"; then
    key="${output%%$'\n'*}"
    case "$key" in
      ctrl-q) quit_to_shell ;;
      ctrl-c) exit 130 ;;
      ctrl-g) LIST_ACTION="manual" ;;
      "")
        LIST_VALUE="${output#*$'\n'}"
        [[ -z "$LIST_VALUE" ]] || LIST_ACTION="value"
        ;;
    esac
  else
    LIST_STATUS=$?
    case "$LIST_STATUS" in
      1|130) ;;
      *) LIST_ACTION="error" ;;
    esac
  fi
  return 0
}

# ------------------------------------------------------------------------------
# 7. Menu Workflows
# ------------------------------------------------------------------------------
select_provider() {
  local title="$1" parent="$2" allow_add="${3:-0}" choice store names line p base i menu_error=''
  local providers=()
  SELECTED_PROVIDER=""
  ensure_store
  while true; do
    providers=()
    show_banner "$title"
    if ! store="$(read_store)"; then
      pause_key "$parent"
      return 0
    fi
    names="$(printf '%s\n' "$store" | jq -r '.providers[].name')"
    while IFS= read -r line; do [[ -n "$line" ]] && providers+=("$line"); done <<< "$names"

    if [[ ${#providers[@]} -eq 0 ]]; then
      printf 'No providers configured.\n\n'
      if [[ "$allow_add" == 0 ]]; then
        pause_key "$parent"
        return 0
      fi
      ui_option Enter/A 'Add New Provider'
    else
      i=1
      for p in "${providers[@]}"; do
        base="$(printf '%s\n' "$store" | jq -r --arg n "$p" '.providers[] | select(.name == $n) | .baseUrl')"
        ui_option "$i" "$p" "$BOLD"
        ui_text "$base" "$GRAY" '      '
        i=$((i + 1))
      done
    fi
    show_navigation "$parent"
    [[ -z "$menu_error" ]] || report_error "$menu_error"
    menu_error=''

    read_menu_choice "Enter selection: "
    case "$choice" in
      [bB]) return 0 ;;
      [qQ]) quit_to_shell ;;
      ""|[aA])
        if [[ "$allow_add" == 1 && ${#providers[@]} -eq 0 ]]; then
          menu_add_provider "$title"
          continue
        fi
        ;;
    esac
    # Compare against displayed keys, without interpreting user input as arithmetic.
    for ((i=0; i<${#providers[@]}; i++)); do
      if [[ "$choice" == "$((i + 1))" ]]; then
        SELECTED_PROVIDER="${providers[$i]}"
        return 0
      fi
    done
    menu_error='Invalid selection.'
  done
}

menu_switch_agent() {
  local agent="$1" title="Switch Claude Code" SELECTED_PROVIDER
  [[ "$agent" == "codex" ]] && title="Switch Codex CLI"
  while true; do
    select_provider "${title} - Select Provider" "Main Menu" 1
    [[ -n "$SELECTED_PROVIDER" ]] || return 0
    menu_pick_model "$agent" "$SELECTED_PROVIDER"
  done
}

menu_pick_model() {
  local agent="$1" provider="$2"
  local agent_title="Claude Code"
  [[ "$agent" == "codex" ]] && agent_title="Codex CLI"

  local p_json
  if ! p_json="$(read_provider "$provider")"; then
    pause_key "Provider Selection"
    return 0
  fi
  local base_url api_key models_url
  base_url="$(echo "$p_json" | jq -r '.baseUrl')"
  api_key="$(echo "$p_json" | jq -r '.apiKey')"
  models_url="$(echo "$p_json" | jq -r '.modelsUrl // empty')"

  show_banner "${agent_title} - Select Model"
  if [[ -n "$models_url" ]]; then
    ui_text "Querying live models from $provider..." "$CYAN"
  else
    ui_text "Live model discovery is not configured for $provider." "$CYAN"
    ui_text 'Enter the model ID manually; no endpoint will be inferred.'
  fi

  local fetched_models=""
  if [[ -n "$models_url" ]] && fetched_models="$(fetch_models "$models_url" "$api_key")"; then
    [[ -n "$fetched_models" ]] || ui_text 'The model endpoint returned an empty list.'
  else
    fetched_models=""
  fi

  local sel_model LIST_ACTION LIST_VALUE LIST_STATUS FORM_ACTION FORM_VALUE redraw=0 back_target
  while true; do
    if [[ "$redraw" == 1 ]]; then show_banner "${agent_title} - Select Model"; fi
    redraw=1
    sel_model=""
    if [[ -n "$fetched_models" ]]; then
      ui_text "Provider: $provider" "$CYAN"
      show_navigation 'Provider Selection' list
      read_list_choice --prompt='Model > ' \
        --no-multi --expect=ctrl-q,ctrl-c,ctrl-g <<< "$fetched_models"
      case "$LIST_ACTION" in
        value) sel_model="$LIST_VALUE" ;;
        back) return 0 ;;
        error)
          report_error "Model picker failed (fzf exit $LIST_STATUS)."
          pause_key "Provider Selection"
          return 0 ;;
      esac
    fi

    if [[ -z "$sel_model" ]]; then
      back_target='Provider Selection'
      if [[ -n "$fetched_models" ]]; then
        show_banner "${agent_title} - Enter Model"
        back_target='Model List'
      fi
      ui_text "Provider: $provider" "$CYAN"
      if [[ -t 0 && -t 1 && "${TERM:-dumb}" != dumb ]]; then
        show_navigation "$back_target" form
      else
        show_navigation "$back_target" result
      fi
      ui_text 'Model ID:' "$BOLD"
      read_form_value '> '
      sel_model="$(trim_whitespace "$FORM_VALUE")"
      if [[ "$FORM_ACTION" == back || -z "$sel_model" ]]; then
        [[ -n "$fetched_models" ]] || return 0
        continue
      fi
    fi

    if [[ "$agent" == "claude" ]]; then
      apply_claude "$provider" "$sel_model" "$base_url" "$api_key"
    else
      apply_codex "$provider" "$sel_model" "$base_url" "$api_key"
    fi

    show_banner "Configuration Applied"
    ui_text "Successfully switched ${agent_title}." "$GREEN"
    ui_text "Model: $sel_model"
    ui_text "Provider: $provider"
    ui_text "Base URL: $base_url"
    pause_key "Provider Selection"
    return 0
  done
}

menu_add_provider() {
  local parent="${1:-Provider Management}" store status
  ensure_store
  show_banner "Add New Provider"
  if ! store="$(read_store)"; then
    pause_key "$parent"
    return 0
  fi

  local labels=('Provider Name' 'Base URL' 'Models URL (optional)' 'API Key (hidden)')
  local values=() step=0 secret FORM_ACTION FORM_VALUE
  local redraw=0 back_target form_error=""
  while [[ "$step" -lt 4 ]]; do
    [[ "$redraw" -eq 0 ]] || show_banner "Add New Provider"
    redraw=1
    ui_text "Step $((step + 1)) of 4"
    [[ "$step" == 0 ]] || ui_text "Provider: ${values[0]}" "$GRAY"
    back_target="$parent"
    [[ "$step" -eq 0 ]] || back_target="${labels[$((step - 1))]}"
    secret=0
    [[ "$step" -ne 3 ]] || secret=1
    if [[ "$secret" == 0 && -t 0 && -t 1 && "${TERM:-dumb}" != dumb ]]; then
      show_navigation "$back_target" form
    else
      show_navigation "$back_target"
    fi
    [[ -z "$form_error" ]] || report_error "$form_error"
    form_error=""

    ui_text "${labels[$step]}:" "$BOLD"
    read_form_value '> ' "$secret" "${values[$step]:-}"
    if [[ "$FORM_ACTION" == back ]]; then
      [[ "$step" -gt 0 ]] || return 0
      step=$((step - 1))
      continue
    fi
    values[$step]="$FORM_VALUE"
    FORM_VALUE="$(trim_whitespace "$FORM_VALUE")"
    if [[ "$step" -ne 2 && -z "$FORM_VALUE" ]]; then
      form_error="${labels[$step]} is required."
      continue
    fi
    if [[ "$step" -eq 1 || ( "$step" -eq 2 && -n "$FORM_VALUE" ) ]] && ! is_http_url "$FORM_VALUE"; then
      form_error="${labels[$step]} must start with http:// or https://."
      continue
    fi
    values[$step]="$FORM_VALUE"
    step=$((step + 1))
  done
  local name="${values[0]}" base_url="${values[1]}" models_url="${values[2]}" api_key="${values[3]}"

  acquire_lock
  if ! store="$(read_store)"; then
    release_lock
    pause_key "$parent"
    return 0
  fi
  local now; now="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  local updated
  if updated="$(printf '%s\n' "$store" | jq -ce --arg n "$name" --arg b "$base_url" --arg k "$api_key" --arg m "$models_url" --arg t "$now" '
    .providers |= (map(select(.name != $n)) + [{
      name: $n, baseUrl: $b, apiKey: $k,
      modelsUrl: (if $m == "" then null else $m end),
      createdAt: $t
    }])' 2>/dev/null)"; then
    atomic_deploy "$CONFIG_FILE" "$updated"
  else
    status=$?
    report_error "Could not save provider (jq exit $status)."
    release_lock
    pause_key "$parent"
    return 0
  fi
  release_lock

  show_banner 'Provider Saved'
  ui_text "Provider \"$name\" saved successfully." "$GREEN"
  pause_key "$parent"
}

menu_remove_provider() {
  local SELECTED_PROVIDER store updated status
  while true; do
    select_provider "Remove Provider" "Provider Management"
    [[ -n "$SELECTED_PROVIDER" ]] || return 0
    acquire_lock
    if ! store="$(read_store)"; then
      release_lock
      pause_key "Provider Management"
      return 0
    fi
    if updated="$(printf '%s\n' "$store" | jq -ce --arg n "$SELECTED_PROVIDER" '
      if any(.providers[]; .name == $n) then .providers |= map(select(.name != $n))
      else error("provider no longer exists") end
    ' 2>/dev/null)"; then
      atomic_deploy "$CONFIG_FILE" "$updated"
    else
      status=$?
      report_error "Could not remove the selected provider (jq exit $status)."
      release_lock
      pause_key "Remove Provider"
      continue
    fi
    release_lock
    show_banner 'Provider Removed'
    ui_text "Removed provider: $SELECTED_PROVIDER" "$GREEN"
    pause_key "Remove Provider"
  done
}

menu_list_providers() {
  local store
  ensure_store
  show_banner "Configured Providers"
  if ! store="$(read_store)"; then
    pause_key "Provider Management"
    return 0
  fi
  if [[ "$(printf '%s\n' "$store" | jq '.providers | length')" == 0 ]]; then
    ui_text 'No providers configured.'
  fi
  printf '%s\n' "$store" | jq -r '.providers[] | "\(.name)\t\(.baseUrl)\t\(.modelsUrl // "NOT CONFIGURED")\t\(.createdAt // "-")"' | while IFS=$'\t' read -r name base models created; do
    ui_text "$name" "$CYAN" '  '
    ui_text "Base URL: $base" "$GRAY" '    '
    ui_text "Models URL: $models" "$GRAY" '    '
    ui_text "Created: $created" "$GRAY" '    '
    echo ""
  done
  pause_key "Provider Management"
}

menu_inspect_models() {
  local SELECTED_PROVIDER p_json api_key models_url fetched_models m
  local models=()
  while true; do
    select_provider "Inspect Models - Select Provider" "Provider Management"
    [[ -n "$SELECTED_PROVIDER" ]] || return 0
    if ! p_json="$(read_provider "$SELECTED_PROVIDER")"; then
      pause_key "Inspect Models - Select Provider"
      continue
    fi
    api_key="$(printf '%s' "$p_json" | jq -r '.apiKey')"
    models_url="$(printf '%s' "$p_json" | jq -r '.modelsUrl // empty')"

    show_banner 'Live Models'
    ui_text "Provider: $SELECTED_PROVIDER" "$CYAN"
    if [[ -z "$models_url" ]]; then
      ui_text "Live model discovery is not configured for $SELECTED_PROVIDER." "$CYAN"
      ui_text 'No request was made because this provider has no Models URL.'
    else
      ui_text "Querying live models from $SELECTED_PROVIDER..." "$CYAN"
      if fetched_models="$(fetch_models "$models_url" "$api_key")"; then
        models=()
        while IFS= read -r m; do [[ -n "$m" ]] && models+=("$m"); done <<< "$fetched_models"
        if [[ ${#models[@]} -gt 0 ]]; then
          for m in "${models[@]}"; do ui_text "$m" '' '  '; done
          ui_text "Total: ${#models[@]} models found." "$GREEN"
        else
          ui_text 'The model endpoint returned an empty list.'
        fi
      fi
    fi
    pause_key "Inspect Models - Select Provider"
  done
}

menu_manage_providers() {
  local choice menu_error=''
  while true; do
    show_banner "Provider Management"
    ui_option 1 'Add New Provider'
    ui_option 2 'List All Providers'
    ui_option 3 'Remove a Provider'
    ui_option 4 'Query Live Models from Provider'
    ui_option 5 'Clear Configuration Files'
    show_navigation 'Main Menu'
    [[ -z "$menu_error" ]] || report_error "$menu_error"
    menu_error=''

    read_menu_choice 'Enter choice: '
    case "$choice" in
      1) menu_add_provider ;;
      2) menu_list_providers ;;
      3) menu_remove_provider ;;
      4) menu_inspect_models ;;
      5) menu_reset_store ;;
      [bB]) return 0 ;;
      [qQ]) quit_to_shell ;;
      *) menu_error='Invalid selection.' ;;
    esac
  done
}

menu_reset_store() {
  local choice target menu_error=''
  while true; do
    show_banner 'Clear Configuration Files'
    if ! target="$(reset_store_target)"; then
      pause_key 'Provider Management'
      return 0
    fi
    ui_text "$target" '' '  '
    printf '\n'
    ui_text 'All saved providers and API keys will be cleared.'
    printf '\n'
    ui_option C 'Clear Configuration Files' "$RED"
    show_navigation 'Provider Management' result
    [[ -z "$menu_error" ]] || report_error "$menu_error"
    menu_error=''
    read_menu_choice 'Enter choice: '
    case "$choice" in
      [cC]) break ;;
      ""|[bB]) return 0 ;;
      [qQ]) quit_to_shell ;;
      *) menu_error='Invalid selection.' ;;
    esac
  done
  show_banner 'Clear Configuration Files'
  if reset_store; then
    ui_text 'avoid configuration cleared.' "$GREEN"
  fi
  pause_key 'Provider Management'
}

# ------------------------------------------------------------------------------
# 8. Main TUI Loop
# ------------------------------------------------------------------------------
tui_main() {
  local choice menu_error=''
  if [[ ! -t 0 ]]; then exec 0</dev/tty; fi
  while true; do
    show_banner "avoid - Coding Agent Configuration Manager"
    ui_option 1 'Switch Claude Code Configuration'
    ui_option 2 'Switch OpenAI Codex CLI Configuration'
    ui_option 3 'Manage Providers'
    show_navigation
    [[ -z "$menu_error" ]] || report_error "$menu_error"
    menu_error=''

    read_menu_choice 'Enter choice: '
    case "$choice" in
      1) menu_switch_agent "claude" ;;
      2) menu_switch_agent "codex" ;;
      3) menu_manage_providers ;;
      [qQ]) quit_to_shell ;;
      *) menu_error='Invalid selection.' ;;
    esac
  done
}

main() {
  check_dependencies
  trap cleanup EXIT
  trap 'exit 130' INT
  trap 'exit 143' TERM
  tui_main
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main
fi
