#!/usr/bin/env bash
# @desc: 代理开关 —— 只对当前 shell 生效，关掉终端即恢复
# ==============================================================
#  px — 代理开关（只作用于当前 shell，不写任何配置文件）
#
#  用法:
#     source <(curl -sL t.melulu.top/px)          开启代理（仅当前终端）
#     source <(curl -sL t.melulu.top/px) -n       关闭代理（当前终端）
#     source <(curl -sL t.melulu.top/px) --save   写入 rc，永久生效
#     source <(curl -sL t.melulu.top/px) --drop   从 rc 移除，恢复原本
#     source <(curl -sL t.melulu.top/px) --show   查看当前代理状态
#     source <(curl -sL t.melulu.top/px) --test   开启并做连通性测试
#
#  等价于手动执行:
#     export http_proxy=http://192.168.0.221:7890
#     export https_proxy=http://192.168.0.221:7890
#     export socks5_proxy=socks5://192.168.0.221:7891
#     export no_proxy=localhost,127.0.0.1,::1,.local
#     （大写变量同步设置，兼容只认大写的工具）
#
#  ⚠ 必须用 source，不能用 bash:
#     bash <(curl ...) 在子进程里跑，export 随进程退出而失效，
#     对当前终端毫无影响 —— 看起来执行了其实没生效。
#     source <(...) 才作用于当前 shell。关掉终端即自动恢复原本。
#
#  ⚠ 默认【不写】任何配置文件。纯环境变量，开关都在当前 shell 内完成。
#     只有显式加 --save 才写 ~/.bashrc（并打通登录 shell 链条），
#     --drop 才把它移除。
#
#  端口（按 mihomo 实测结果）:
#     http   -> 192.168.0.221:7890   （实测 7890 只支持 http）
#     socks5 -> 192.168.0.221:7891   （实测 7891 只支持 socks5）
#     mihomo 的 mixed-port 才能同时听两种协议；把 socks5 也指向 7890
#     会挂住无响应（curl 返回 000 超时）。要覆盖就自己传环境变量:
#       HTTP_PROXY=http://h:7890 SOCKS5_PROXY=socks5://h:7890 \
#         source <(curl -sL t.melulu.top/px)
#
#  可覆盖: HTTP_PROXY / SOCKS5_PROXY / NO_PROXY（已设过则沿用，不覆盖）
# ==============================================================

# ---------- 检测是被 source 还是被 bash 执行 ----------
# 被 source 时 return 可用；被 bash 执行时 return 会报错。
(return 0 2>/dev/null) && PX_SOURCED=1 || PX_SOURCED=0

# ---- 帮助文本 ----
# 不能用 `sed -n ... "$0"`：经 `bash <(curl ...)` / `source <(curl ...)`
# 运行时 $0 是已关闭的 /dev/fd/63，读不到自身。所以这里把头部注释
# 先存进变量，--help 时直接打印。
PX_HELP='  px — 代理开关（只作用于当前 shell，不写任何配置文件）

用法:
  source <(curl -sL t.melulu.top/px)          开启代理（仅当前终端）
  source <(curl -sL t.melulu.top/px) -n       关闭代理（当前终端）
  source <(curl -sL t.melulu.top/px) --save   写入 ~/.bashrc，永久生效
  source <(curl -sL t.melulu.top/px) --drop   从 ~/.bashrc 移除，恢复原本
  source <(curl -sL t.melulu.top/px) --show   查看当前代理状态
  source <(curl -sL t.melulu.top/px) --test   开启并做连通性测试
  source <(curl -sL t.melulu.top/px) --help   本帮助

等价于手动执行:
  export http_proxy=http://192.168.0.221:7890
  export https_proxy=http://192.168.0.221:7890
  export socks5_proxy=socks5://192.168.0.221:7891
  export no_proxy=localhost,127.0.0.1,::1,.local
  （大写变量同步设置，兼容只认大写的工具）

必须用 source，不能用 bash:
  bash <(curl ...) 在子进程里跑，export 随进程退出而失效，
  对当前终端毫无影响 —— 看起来执行了其实没生效。
  source <(...) 才作用于当前 shell。关掉终端即自动恢复原本。

默认【不写】任何配置文件。只有显式加 --save 才写 ~/.bashrc，
--drop 才把它移除。

端口（按 mihomo 实测结果）:
  http   -> 192.168.0.221:7890   （实测 7890 只支持 http）
  socks5 -> 192.168.0.221:7891   （实测 7891 只支持 socks5）
  mihomo 的 mixed-port 才能同时听两种协议；把 socks5 也指向 7890
  会挂住无响应（curl 返回 000 超时）。要覆盖就自己传环境变量:
    HTTP_PROXY=http://h:7890 SOCKS5_PROXY=socks5://h:7890 \
      source <(curl -sL t.melulu.top/px)

可覆盖: HTTP_PROXY / SOCKS5_PROXY / NO_PROXY（已设过则沿用，不覆盖）'

HTTP_PROXY="${HTTP_PROXY:-${http_proxy:-http://192.168.0.221:7890}}"
SOCKS5_PROXY="${SOCKS5_PROXY:-${socks5_proxy:-socks5://192.168.0.221:7891}}"
NO_PROXY="${NO_PROXY:-${no_proxy:-localhost,127.0.0.1,::1,.local}}"

MODE="on"
TEST=0
for a in "$@"; do
  case "$a" in
    -n|--no|--off|--clear|--unset|--disable) MODE="off" ;;
    --save|--permanent|-w|--write)           MODE="save" ;;
    --drop|--uninstall|--purge)              MODE="drop" ;;
    --show|--status)                         MODE="show" ;;
    --test)                                  TEST=1 ;;
    -h|--help)  printf '%s\n' "$PX_HELP" | sed 's/^# \{0,1\}//'
                 exit 0 ;;
  esac
done

# ---------- 写 rc / 从 rc 移除时用到的标记 ----------
BEG='# >>> px >>>'
END='# <<< px <<<'

strip_block() {
  [ -f "$1" ] || return 0
  grep -qF "$BEG" "$1" 2>/dev/null || return 0
  awk -v b="$BEG" -v e="$END" '$0==b{s=1;next} $0==e{s=0;next} !s{print}' "$1" \
    > "$1.tmp.$$" 2>/dev/null && mv -f "$1.tmp.$$" "$1"
  awk '/^[[:space:]]*$/{if(b)next;b=1;print;next}{b=0;print}' "$1" \
    > "$1.tmp.$$" 2>/dev/null && mv -f "$1.tmp.$$" "$1"
  return 0
}

rc_target() {
  if [ -n "${PROXY_RC:-}" ]; then echo "$PROXY_RC"; return; fi
  case "${SHELL:-}" in
    */zsh|?zsh) echo "$HOME/.zshrc" ;;
    *)          echo "$HOME/.bashrc" ;;
  esac
}

plat_cn() {
  case "$(uname -s 2>/dev/null)" in
    MINGW*|MSYS*|CYGWIN*) echo 'Windows (Git-Bash/MSYS)' ;;
    Darwin)                echo 'macOS' ;;
    Linux)
      if grep -qiE "microsoft|wsl" /proc/version 2>/dev/null; then
        echo 'WSL (Linux 子系统)'
      else echo 'Linux'; fi ;;
    *) echo "$(uname -s 2>/dev/null || 未知)" ;;
  esac
}

# ==============================================================
# 开启
# ==============================================================
do_on() {
  export http_proxy="$HTTP_PROXY"
  export https_proxy="$HTTP_PROXY"
  export socks5_proxy="$SOCKS5_PROXY"
  export no_proxy="$NO_PROXY"
  # 大写同步：curl/wget/npm/git 各有只认大写或只认小写的情况
  export HTTP_PROXY="$http_proxy"
  export HTTPS_PROXY="$https_proxy"
  export SOCKS5_PROXY="$socks5_proxy"
  export ALL_PROXY="$socks5_proxy"
  export NO_PROXY="$no_proxy"

  echo "  http_proxy   = $http_proxy"
  echo "  https_proxy  = $https_proxy"
  echo "  socks5_proxy = $socks5_proxy"
  echo "  no_proxy     = $no_proxy"

  if [ "$TEST" = "1" ] && command -v curl >/dev/null 2>&1; then
    echo
    echo "连通性测试:"
    printf '  经 http   代理: '
    curl -s --max-time 8 -o /dev/null -w '%{http_code}\n' http://example.com || echo 失败
    printf '  经 socks5 代理: '
    curl -s --max-time 8 -o /dev/null -w '%{http_code}\n' -x "$SOCKS5_PROXY" http://example.com || echo 失败
  fi

  cat <<TIP

  已开启 —— 仅当前终端有效，关掉终端或执行下面这行即恢复原本：
      source <(curl -sL t.melulu.top/px) -n

  可用参数:
      -n / --off        关闭代理（当前终端）
      --show            查看当前代理状态
      --test            开启并做连通性测试
      --save            写入 ~/.bashrc，永久生效
      --drop            从 ~/.bashrc 移除，恢复原本
      --help            完整说明

  示例:
      source <(curl -sL t.melulu.top/px) --test      开启并测速
      source <(curl -sL t.melulu.top/px) --show      只看当前状态
      source <(curl -sL t.melulu.top/px) --save      永久启用
      source <(curl -sL t.melulu.top/px) -n           关闭
TIP
}

# ==============================================================
# --save：写入 rc，永久生效
# ==============================================================
do_save() {
  local rc body
  rc="$(rc_target)"
  body="$BEG
# 由 px 脚本写入。移除请执行: source <(curl -sL t.melulu.top/px) --drop
export http_proxy=\"$HTTP_PROXY\"
export https_proxy=\"$HTTP_PROXY\"
export socks5_proxy=\"$SOCKS5_PROXY\"
export no_proxy=\"$NO_PROXY\"
export HTTP_PROXY=\"\$http_proxy\"
export HTTPS_PROXY=\"\$https_proxy\"
export SOCKS5_PROXY=\"\$socks5_proxy\"
export ALL_PROXY=\"\$socks5_proxy\"
export NO_PROXY=\"\$no_proxy\"
$END"

  mkdir -p "$(dirname "$rc")" 2>/dev/null
  if [ ! -f "$rc" ]; then
    : > "$rc" 2>/dev/null || { echo "无法创建 $rc" >&2; return 1; }
  fi
  [ -w "$rc" ] || { echo "无权写入 $rc" >&2; return 1; }
  strip_block "$rc"
  printf '\n%s\n' "$body" >> "$rc"
  echo "已写入 $rc（新开终端自动生效）"

  # zsh 用户同时写 .zshrc，避免换 shell 就失效
  if [ "${SHELL:-}" != "${SHELL:-}/zsh" ] && [ -f "$HOME/.zshrc" ]; then
    strip_block "$HOME/.zshrc"
    printf '\n%s\n' "$body" >> "$HOME/.zshrc"
    echo "已写入 $HOME/.zshrc"
  fi

  # 登录 shell 链条：SSH 进来是登录 shell，只读 ~/.bash_profile / ~/.profile，
  # 不直接读 ~/.bashrc。这两个文件常缺失（容器里 HOME 改过就是这情况），
  # 链条一断则代理设了也等于没设 —— 重开 SSH 就失效。
  for bp in "$HOME/.profile" "$HOME/.bash_profile"; do
    [ -f "$bp" ] && grep -qF "$rc" "$bp" 2>/dev/null && continue
    if [ ! -f "$bp" ]; then
      { printf '# 由 px 脚本创建：登录 shell 需要它才能加载 %s\n' "$rc"
        printf '[ -f "%s" ] && . "%s"\n' "$rc" "$rc"; } > "$bp" 2>/dev/null \
        && echo "已创建 $bp（登录 shell 链条）"
    else
      printf '\n[ -f "%s" ] && . "%s"\n' "$rc" "$rc" >> "$bp" 2>/dev/null \
        && echo "已在 $bp 补上加载语句"
    fi
  done
  return 0
}

# ==============================================================
# --drop：从 rc 移除
# ==============================================================
do_drop() {
  local n=0 rc
  for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" \
            "$HOME/.bash_profile" "$HOME/.zprofile" "${PROXY_RC:-}"; do
    [ -n "$rc" ] && [ -f "$rc" ] || continue
    if grep -qF "$BEG" "$rc" 2>/dev/null; then
      strip_block "$rc"; echo "已从 $rc 移除"; n=$((n+1))
    fi
  done
  # 注意: 结尾必须显式 return 0。`[ "$n" = "0" ] && echo ...` 在条件为假时
  # 返回 1, 会成为整个函数的退出码 —— 上游耗时提示会误报 [FAIL] exit=1。
  [ "$n" = "0" ] && echo "rc 文件里没有 px 配置（本来就是临时的）"
  return 0
}

# ==============================================================
# 关闭
# ==============================================================
do_off() {
  unset http_proxy https_proxy socks5_proxy no_proxy
  unset HTTP_PROXY HTTPS_PROXY SOCKS5_PROXY ALL_PROXY NO_PROXY
  echo "已关闭代理，恢复原本"
  # 顺带把 rc 里的永久配置也清掉（-n 是"完全恢复原本"的语义）
  local _rc; _rc="$(rc_target)"
  if [ -f "$_rc" ] && grep -qF "$BEG" "$_rc" 2>/dev/null; then
    do_drop
  else
    echo "（rc 文件里没有 px 配置，无需清理）"
  fi
}

# ==============================================================
# 查看
# ==============================================================
do_show() {
  printf '平台: %s\n' "$(plat_cn)"
  echo
  printf '  %-14s = %s\n' "http_proxy"   "${http_proxy:-（未设置）}"
  printf '  %-14s = %s\n' "https_proxy"  "${https_proxy:-（未设置）}"
  printf '  %-14s = %s\n' "socks5_proxy" "${socks5_proxy:-（未设置）}"
  printf '  %-14s = %s\n' "no_proxy"     "${no_proxy:-（未设置）}"
  printf '  %-14s = %s\n' "HTTP_PROXY"   "${HTTP_PROXY:-（未设置）}"
  printf '  %-14s = %s\n' "SOCKS5_PROXY" "${SOCKS5_PROXY:-（未设置）}"
  echo
  if [ -n "${http_proxy:-}" ]; then
    echo "状态: 代理已开启（仅当前终端）"
  else
    echo "状态: 未开启"
  fi
}

# ---------- 未被 source 的警告 ----------
warn_not_sourced() {
  [ "$PX_SOURCED" = "1" ] && return 0
  cat >&2 <<'W'

  ⚠ 你用的是 bash 执行，本脚本 export 的变量会随子进程退出而失效，
    当前终端没有任何变化。要真正生效请改用：

        source <(curl -sL t.melulu.top/px)

W
}

# 退出码必须显式给 0: 分支末尾若是 `[ x ] && cmd` 这类条件, 条件为假时
# 返回 1, 会成为整个脚本的退出码 —— 上游 shinit 的耗时提示就会误报
# "[FAIL] exit=1"(实际命令是成功的)。
#
# 用 return 而不是 exit: 被 source 时 `exit` 会终止【调用方函数】(比如 wcls),
# 导致它后面的 shell 状态还原代码永远不执行、函数卡住。return 只结束
# 本脚本自身, 对 source 的宿主完全无害。单独 bash 执行时 return 在
# 顶层等价于脚本结束, 行为一致。
case "$MODE" in
  show) do_show; return 0 ;;
  off)  do_off
        [ "$PX_SOURCED" = "0" ] && printf '（注意：非 source 执行，当前 shell 的变量未改变）\n'
        return 0 ;;
  drop) do_drop; return 0 ;;
  save) do_on          # 先让当前 shell 生效
        echo
        do_save
        return 0 ;;
  on)   warn_not_sourced
        do_on
        return 0 ;;
esac