#!/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 ]] && [[ "${NO_COLOR:-0}" != "1" ]]; then
  NORMAL="\033[0m"
  BOLD="\033[1m"
  DIM="\033[2m"
  MENU="\033[1;34m"      # Bright Blue/Cyan Box Borders
  NUMBER="\033[1;33m"    # Yellow for Menu Numbers/Keys
  GREEN="\033[1;32m"     # Bright Green for Active Status
  RED="\033[1;31m"       # Red for Errors / Critical
  GRAY="\033[1;30m"      # Gray/Dim for Subtitles
  CYAN="\033[1;36m"        # Cyan for Highlights
  ENTER_LINE="\033[1;33m"  # Yellow Prompt
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"
LOCK_DIR="${CONFIG_DIR}/.lock"

# Check required runtime dependencies
for req in curl jq; do
  command -v "$req" >/dev/null 2>&1 || { echo -e "\033[1;31mError: '$req' is required. Please install it (e.g. sudo apt install $req).\033[0m" >&2; exit 1; }
done

# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# 2. Storage, Concurrency Lock & Atomic Swap
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
ensure_store() {
  [[ -d "$CONFIG_DIR" ]] || { mkdir -p "$CONFIG_DIR" && chmod 700 "$CONFIG_DIR"; }
  [[ -f "$CONFIG_FILE" ]] || { printf '{\n  "auth": []\n}\n' > "$CONFIG_FILE" && chmod 600 "$CONFIG_FILE"; }
}

acquire_lock() {
  local count=0
  while ! mkdir "$LOCK_DIR" 2>/dev/null; do
    sleep 0.05
    count=$((count + 1))
    [[ $count -ge 60 ]] && { echo "Error: Lock timeout" >&2; exit 1; }
  done
  trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT INT TERM
}

release_lock() {
  rmdir "$LOCK_DIR" 2>/dev/null || true
  trap - EXIT INT TERM
}

atomic_deploy() {
  local target_file="$1" content="$2"
  local parent_dir
  parent_dir="$(dirname "$target_file")"
  mkdir -p "$parent_dir" && chmod 700 "$parent_dir"

  local tmp_file
  tmp_file="$(mktemp "${parent_dir}/.swp.XXXXXXXX")"
  chmod 600 "$tmp_file"
  trap 'rm -f "$tmp_file" 2>/dev/null || true' EXIT INT TERM

  printf "%s\n" "$content" > "$tmp_file"
  mv -f "$tmp_file" "$target_file"
  trap - EXIT INT TERM
}

# ------------------------------------------------------------------------------
# 4. Model Discovery & Upstream Handshake
# ------------------------------------------------------------------------------
fetch_models() {
  local base_url="$1" models_url="$2" api_key="$3"
  local target_url="${models_url:-$base_url}"
  [[ "$target_url" != *"/models"* ]] && target_url="${target_url%/}/models"

  local resp
  resp="$(curl -s -f -m 10 \
    -H "Authorization: Bearer ${api_key}" \
    -H "x-api-key: ${api_key}" \
    "$target_url" 2>/dev/null || true)"

  if [[ -n "$resp" ]]; then
    echo "$resp" | jq -r '(.data[]?.id // .[]?.id // .[]?)' 2>/dev/null | sort -u
  fi
}

# ------------------------------------------------------------------------------
# 5. Agent Template Applicators
# ------------------------------------------------------------------------------
apply_claude() {
  local provider_name="$1" model="$2" base_url="$3" api_key="$4"
  local target="${HOME}/.claude/settings.json"
  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="${HOME}/.codex/config.toml"
  local safe_b="${base_url//\"/\\\"}" safe_k="${api_key//\"/\\\"}" safe_m="${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"
}

# ------------------------------------------------------------------------------
# 6. UI Banner & Layout Functions (MrChromebox Centered Box Design)
# ------------------------------------------------------------------------------
clear_screen() {
  printf "\033c"
}

show_banner() {
  local title="${1:-avoid - Coding Agent Configuration Manager}"
  clear_screen
  echo -e "${MENU}*******************************************************************************${NORMAL}"
  local inner_width=75
  local title_len=${#title}
  local pad_left=$(( (inner_width - title_len) / 2 ))
  local pad_right=$(( inner_width - title_len - pad_left ))
  printf "${MENU}**${NORMAL}%*s${BOLD}%s${NORMAL}%*s${MENU}**${NORMAL}\n" "$pad_left" "" "$title" "$pad_right" ""
  echo -e "${MENU}*******************************************************************************${NORMAL}\n"
}

show_divider() {
  echo -e "${MENU}*******************************************************************************${NORMAL}"
}

pause_key() {
  local prompt="${1:-Press [Enter] to continue...}"
  echo ""
  show_divider
  if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then
    exec 0</dev/tty
  fi
  read -e -r -p "$prompt" _
}

# ------------------------------------------------------------------------------
# 7. Menu Workflows
# ------------------------------------------------------------------------------
menu_switch_agent() {
  local agent="$1"
  local title="Switch Claude Code"
  [[ "$agent" == "codex" ]] && title="Switch Codex CLI"

  ensure_store
  local providers=()
  while IFS= read -r line; do [[ -n "$line" ]] && providers+=("$line"); done < <(jq -r '.auth[].name' "$CONFIG_FILE" 2>/dev/null || true)

  if [[ ${#providers[@]} -eq 0 ]]; then
    show_banner "$title"
    echo -e "${RED}No providers configured yet.${NORMAL}"
    echo -e "Please add a provider first.\n"
    pause_key "Press [Enter] to add a provider..."
    menu_add_provider
    return
  fi

  show_banner "${title} - Select Provider"
  local i=1
  for p in "${providers[@]}"; do
    local base
    base="$(jq -r --arg n "$p" '.auth[] | select(.name == $n) | .baseUrl' "$CONFIG_FILE")"
    echo -e "  [${NUMBER}${i}${NORMAL}] ${BOLD}${p}${NORMAL} ${GRAY}(${base})${NORMAL}"
    ((i++))
  done
  echo ""
  echo -e "  [${NUMBER}B${NORMAL}] Back to Main Menu"
  echo -e "  [${NUMBER}Q${NORMAL}] Quit to Shell"
  show_divider

  if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
  read -e -r -p "Enter selection [1-${#providers[@]}, B, Q]: " choice
  case "$choice" in
    [bB]) return ;;
    [qQ]) clear_screen; exit 0 ;;
    *)
      if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#providers[@]} )); then
        local sel_provider="${providers[$((choice - 1))]}"
        menu_pick_model "$agent" "$sel_provider"
      else
        echo -e "\n${RED}Invalid selection.${NORMAL}"
        sleep 1
      fi
      ;;
  esac
}

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

  local p_json
  p_json="$(jq -c --arg n "$provider" '.auth[] | select(.name == $n)' "$CONFIG_FILE")"
  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} - Fetching Models"
  echo -e "${CYAN}Querying live models from ${provider}...${NORMAL}\n"

  local models=()
  while IFS= read -r m; do [[ -n "$m" ]] && models+=("$m"); done < <(fetch_models "$base_url" "$models_url" "$api_key")

  local sel_model=""
  if [[ ${#models[@]} -gt 0 ]]; then
    # Use fzf if available for instant fuzzy search
    if command -v fzf >/dev/null 2>&1; then
      sel_model="$(printf "%s\n" "${models[@]}" | fzf --height=40% --layout=reverse --prompt="Select Model (${provider}) > " --header="[ESC to enter manually]")"
    fi

    # Fallback numbered list if fzf was canceled or unavailable
    if [[ -z "$sel_model" ]]; then
      show_banner "${agent_title} - Select Model (${provider})"
      local max_show=15
      local count=0
      for m in "${models[@]}"; do
        ((count++))
        if (( count <= max_show )); then
          echo -e "  [${NUMBER}${count}${NORMAL}] ${m}"
        fi
      done
      if (( ${#models[@]} > max_show )); then
        echo -e "  ${GRAY}... and $(( ${#models[@]} - max_show )) more models${NORMAL}"
      fi
      echo ""
      echo -e "  [${NUMBER}M${NORMAL}] Enter Model ID manually"
      echo -e "  [${NUMBER}B${NORMAL}] Back to Previous Menu"
      show_divider
      if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
      read -e -r -p "Enter selection [1-${count}, M, B]: " m_choice
      case "$m_choice" in
        [bB]) return ;;
        [mM]) read -e -r -p "Enter Model ID: " sel_model ;;
        *)
          if [[ "$m_choice" =~ ^[0-9]+$ ]] && (( m_choice >= 1 && m_choice <= ${#models[@]} )); then
            sel_model="${models[$((m_choice - 1))]}"
          fi
          ;;
      esac
    fi
  else
    echo -e "${RED}No models returned from endpoint (or network timeout).${NORMAL}"
    if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
    read -e -r -p "Enter Model ID manually: " sel_model
  fi

  sel_model="$(echo "$sel_model" | xargs)"
  [[ -z "$sel_model" ]] && return

  # Deploy configuration
  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"
  echo -e "${GREEN}✓ Successfully switched ${agent_title}!${NORMAL}\n"
  echo -e "  • Target Model : ${BOLD}${sel_model}${NORMAL}"
  echo -e "  • Provider     : ${BOLD}${provider}${NORMAL} ${GRAY}(${base_url})${NORMAL}"
  pause_key "Press [Enter] to return to Main Menu..."
}

menu_add_provider() {
  show_banner "Add New Provider"
  if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi

  local name base_url api_key models_url
  read -e -r -p "1. Provider Name (e.g. relay, deepseek): " name
  read -e -r -p "2. Base URL (e.g. https://api.openai.com/v1): " base_url
  read -r -s -p "3. API Key: " api_key; echo ""
  read -e -r -p "4. Models URL (Optional, press Enter to skip): " models_url

  name="$(echo "$name" | xargs)"
  base_url="$(echo "$base_url" | xargs)"
  api_key="$(echo "$api_key" | xargs)"
  models_url="$(echo "$models_url" | xargs)"

  if [[ -z "$name" || -z "$base_url" || -z "$api_key" ]]; then
    echo -e "\n${RED}Error: Name, Base URL, and API Key are required.${NORMAL}"
    pause_key
    return
  fi

  acquire_lock
  local now; now="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  local updated
  updated="$(jq --arg n "$name" --arg b "$base_url" --arg k "$api_key" --arg m "$models_url" --arg t "$now" '
    .auth |= (map(select(.name != $n)) + [{
      name: $n, baseUrl: $b, apiKey: $k,
      modelsUrl: (if $m == "" or $m == $b then null else $m end),
      createdAt: $t
    }])' "$CONFIG_FILE")"
  atomic_deploy "$CONFIG_FILE" "$updated"
  release_lock

  echo -e "\n${GREEN}✓ Provider \"${name}\" saved successfully.${NORMAL}"
  pause_key
}

menu_remove_provider() {
  ensure_store
  local providers=()
  while IFS= read -r line; do [[ -n "$line" ]] && providers+=("$line"); done < <(jq -r '.auth[].name' "$CONFIG_FILE" 2>/dev/null || true)

  if [[ ${#providers[@]} -eq 0 ]]; then
    show_banner "Remove Provider"
    echo -e "${RED}No providers to remove.${NORMAL}\n"
    pause_key
    return
  fi

  show_banner "Remove Provider"
  local i=1
  for p in "${providers[@]}"; do
    echo -e "  [${NUMBER}${i}${NORMAL}] ${RED}${p}${NORMAL}"
    ((i++))
  done
  echo ""
  echo -e "  [${NUMBER}B${NORMAL}] Back to Previous Menu"
  show_divider

  if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
  read -e -r -p "Enter number to delete [1-${#providers[@]}, B]: " choice
  case "$choice" in
    [bB]) return ;;
    *)
      if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#providers[@]} )); then
        local target="${providers[$((choice - 1))]}"
        acquire_lock
        local updated
        updated="$(jq --arg n "$target" '.auth |= map(select(.name != $n))' "$CONFIG_FILE")"
        atomic_deploy "$CONFIG_FILE" "$updated"
        release_lock
        echo -e "\n${GREEN}✓ Removed provider: ${target}${NORMAL}"
        pause_key
      fi
      ;;
  esac
}

menu_list_providers() {
  show_banner "Configured Providers"
  jq -r '.auth[] | "\(.name)\t\(.baseUrl)\t\(.modelsUrl // "-")\t\(.createdAt // "-")"' "$CONFIG_FILE" | while IFS=$'\t' read -r name base models created; do
    echo -e "  • ${CYAN}${name}${NORMAL}"
    echo -e "    ${GRAY}Base URL  :${NORMAL} ${base}"
    if [[ "$models" != "-" ]]; then
      echo -e "    ${GRAY}Models URL:${NORMAL} ${models}"
    fi
    echo -e "    ${GRAY}Created   :${NORMAL} ${created}"
    echo ""
  done
  pause_key
}

menu_inspect_models() {
  ensure_store
  local providers=()
  while IFS= read -r line; do [[ -n "$line" ]] && providers+=("$line"); done < <(jq -r '.auth[].name' "$CONFIG_FILE" 2>/dev/null || true)

  if [[ ${#providers[@]} -eq 0 ]]; then
    show_banner "Inspect Models"
    echo -e "${RED}No providers configured.${NORMAL}\n"
    pause_key
    return
  fi

  show_banner "Inspect Models - Select Provider"
  local i=1
  for p in "${providers[@]}"; do
    echo -e "  [${NUMBER}${i}${NORMAL}] ${p}"
    ((i++))
  done
  echo ""
  echo -e "  [${NUMBER}B${NORMAL}] Back to Previous Menu"
  show_divider

  if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
  read -e -r -p "Enter selection: " choice
  case "$choice" in
    [bB]) return ;;
    *)
      if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#providers[@]} )); then
        local sel="${providers[$((choice - 1))]}"
        local p_json
        p_json="$(jq -c --arg n "$sel" '.auth[] | select(.name == $n)' "$CONFIG_FILE")"
        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 "Live Models - ${sel}"
        echo -e "${CYAN}Querying live models from ${sel}...${NORMAL}\n"
        local models=()
        while IFS= read -r m; do [[ -n "$m" ]] && models+=("$m"); done < <(fetch_models "$base_url" "$models_url" "$api_key")

        if [[ ${#models[@]} -gt 0 ]]; then
          for m in "${models[@]}"; do
            echo -e "  • ${m}"
          done
          echo -e "\n${GREEN}Total: ${#models[@]} models found.${NORMAL}"
        else
          echo -e "${RED}No models returned from endpoint.${NORMAL}"
        fi
        pause_key
      fi
      ;;
  esac
}

menu_manage_providers() {
  while true; do
    show_banner "Provider Management"
    echo -e "  [${NUMBER}1${NORMAL}] Add New Provider"
    echo -e "  [${NUMBER}2${NORMAL}] List All Providers"
    echo -e "  [${NUMBER}3${NORMAL}] Remove a Provider"
    echo -e "  [${NUMBER}4${NORMAL}] Query Live Models from Provider"
    echo ""
    echo -e "  [${NUMBER}B${NORMAL}] Back to Main Menu"
    echo -e "  [${NUMBER}Q${NORMAL}] Quit to Shell"
    show_divider

    if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
    read -e -r -p "Enter choice [1-4, B, Q]: " choice
    case "$choice" in
      1) menu_add_provider ;;
      2) menu_list_providers ;;
      3) menu_remove_provider ;;
      4) menu_inspect_models ;;
      [bB]) return ;;
      [qQ]) clear_screen; exit 0 ;;
      *) sleep 0.5 ;;
    esac
  done
}

# ------------------------------------------------------------------------------
# 8. Main TUI Loop
# ------------------------------------------------------------------------------
tui_main() {
  while true; do
    show_banner "avoid - Coding Agent Configuration Manager"
    echo -e "  [${NUMBER}1${NORMAL}] Switch Claude Code Configuration (${GRAY}~/.claude/settings.json${NORMAL})"
    echo -e "  [${NUMBER}2${NORMAL}] Switch OpenAI Codex CLI Configuration (${GRAY}~/.codex/config.toml${NORMAL})"
    echo -e "  [${NUMBER}3${NORMAL}] Manage Authentication Providers"
    echo ""
    echo -e "  [${NUMBER}Q${NORMAL}] Quit to Shell"
    show_divider

    if [[ ! -t 0 ]] && [[ -r /dev/tty ]]; then exec 0</dev/tty; fi
    read -e -r -p "Enter choice [1-3, Q]: " choice
    case "$choice" in
      1) menu_switch_agent "claude" ;;
      2) menu_switch_agent "codex" ;;
      3) menu_manage_providers ;;
      [qQ]) clear_screen; exit 0 ;;
      *) sleep 0.5 ;;
    esac
  done
}

tui_main
