#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob

# SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_DIR="$(cd -- "$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" # 指向真正
resolve_mihomo_root() {
  if [[ -n "${MIHOMO_ROOT:-}" ]]; then
    printf "%s\n" "$MIHOMO_ROOT"
  elif [[ -x "${PWD}/mihomo" ]]; then
    printf "%s\n" "$PWD"
  elif [[ "$SCRIPT_DIR" != "/usr/local/bin" && -x "${SCRIPT_DIR}/mihomo" ]]; then
    printf "%s\n" "$SCRIPT_DIR"
  else
    echo "mh: cannot locate mihomo root; cd into the project dir or set MIHOMO_ROOT=/path/to/mihomo" >&2
    exit 1
  fi
}

MIHOMO_ROOT="$(resolve_mihomo_root)"  # 等好后面不能有空格
MIHOMO_BIN=${MIHOMO_ROOT}/mihomo # 指向二进制文件
PID_FILE=${MIHOMO_ROOT}/run.pid # 固定pid文件
THEME_FILE=${MIHOMO_ROOT}/theme.current

API_HOST="127.0.0.1"
API_PORT="9090"
MIXED_PORT="7890" #  默认main
SOCK_PORT="7891"
API_BASE="http://${API_HOST}:${API_PORT}"
API_SECRET="" # 这是密钥，用于验证；对应yaml中设置的secret字段，保持一致即可；如果不设置，默认空字符串，表示不验证

# 简单彩色（不喜欢颜色就把这些变量都设为空字符串）
urlenc() {
  python3 - <<'PY' "$1"
import sys, urllib.parse
print(urllib.parse.quote(sys.argv[1], safe=''))
PY
}



#===============测试小工具，下======================================
theme_current() {
  local theme="basic"
  if [[ -f "$THEME_FILE" ]]; then
    theme="$(tr -d '[:space:]' < "$THEME_FILE")"
  fi
  case "$theme" in
    basic|ganyu|kaomoji|minimal) printf '%s\n' "$theme" ;;
    *) printf 'basic\n' ;;
  esac
}

theme_prefix() {
  local level="$1"
  case "$(theme_current):$level" in
    basic:DEBUG) printf '[debug] ' ;;
    basic:INFO) printf '[info] ' ;;
    basic:WARN) printf '[warn] ' ;;
    basic:ERROR) printf '[error] ' ;;
    basic:OK) printf '[ok] ' ;;

    ganyu:DEBUG) printf '(甘雨观察中) ' ;;
    ganyu:INFO) printf '❄️ 甘雨小秘书：' ;;
    ganyu:WARN) printf '🍧 甘雨提醒：' ;;
    ganyu:ERROR) printf '(甘雨皱眉) ' ;;
    ganyu:OK) printf '🍡 甘雨点头：' ;;

    kaomoji:DEBUG) printf '(눈_눈) ' ;;
    kaomoji:INFO) printf '(｡･ω･｡) ' ;;
    kaomoji:WARN) printf '(；´д｀)ゞ ' ;;
    kaomoji:ERROR) printf '(╯°□°)╯ ' ;;
    kaomoji:OK) printf '(๑•̀ㅂ•́)و✧ ' ;;

    minimal:*) printf '' ;;
    *) printf '' ;;
  esac
}

log() {
    local level=$1
    shift
    echo "[$(date +%H:%M:%S)] [$level] $(theme_prefix "$level")$*"
}
debug() { [[ "${DEBUG:-0}" == "1" ]] && log "DEBUG" "$@"; }
info()  { log "INFO" "$@"; }
warn()  { log "WARN" "$@"; }
error() { log "ERROR" "$@"; }
die()   { error "$@"; exit 1; }
ok()    { log "OK" "$@"; }

need() {
  command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"
}
test_env() { # 测试一下环境
    need ss
    need yq
    need jq
    need curl
    need find
    [[ -x "$MIHOMO_BIN" ]] || die "mihomo not executable: $MIHOMO_BIN"
    echo "环境正确 ; env is ok"
}
test_port() {
  local port
  for port in "$@"; do
    if ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(:|\\.)${port}\$"; then
      die "port already in use: $port"
    fi
  done
}
api_get() {
  local path="$1"
  if [[ -n "${API_SECRET}" ]]; then
    curl -fsS --noproxy '*' -H "Authorization: Bearer ${API_SECRET}" "${API_BASE}${path}"
  else
    curl -fsS --noproxy '*' "${API_BASE}${path}"
  fi
}

api_put() {
  local path="$1"
  local data="$2"
  if [[ -n "${API_SECRET}" ]]; then
    curl -fsS --noproxy '*' -X PUT \
      -H "Authorization: Bearer ${API_SECRET}" \
      -H 'Content-Type: application/json' \
      -d "$data" "${API_BASE}${path}"
  else
    curl -fsS --noproxy '*' -X PUT \
      -H 'Content-Type: application/json' \
      -d "$data" "${API_BASE}${path}"
  fi
}

api_put_empty() {
  local path="$1"
  if [[ -n "${API_SECRET}" ]]; then
    curl -fsS --noproxy '*' -X PUT \
      -H "Authorization: Bearer ${API_SECRET}" "${API_BASE}${path}"
  else
    curl -fsS --noproxy '*' -X PUT "${API_BASE}${path}"
  fi
}

sync_global_group() {
  local pjson
  pjson="$(api_get "/proxies" 2>/dev/null)" || return 1

  jq -e '.proxies["GLOBAL"]?.all? | index("Proxy") != null' >/dev/null <<<"$pjson" || return 0

  local now
  now="$(jq -r '.proxies["GLOBAL"].now // empty' <<<"$pjson")"
  if [[ "$now" != "Proxy" ]]; then
    api_put "/proxies/GLOBAL" '{"name":"Proxy"}' >/dev/null
    info "Synced global selector [GLOBAL] -> [Proxy]"
  fi
}


api_delay() {
  local name="$1"
  local timeout="${2:-2000}"
  local url="http://www.gstatic.com/generate_204"
  local enc_name enc_url
  enc_name="$(urlenc "$name")"
  enc_url="$(urlenc "$url")"
  local out
  out="$(api_get "/proxies/${enc_name}/delay?timeout=${timeout}&url=${enc_url}" 2>/dev/null || true)"
  jq -r 'if (.delay|type)=="number" then (.delay|tostring) else "-" end' <<<"$out" 2>/dev/null || echo "-"
}
switch_group_node() {
  # 用法：switch_group_node "<group>" "<node>"
  local group="$1"
  local node="$2"
  [[ -n "$group" && -n "$node" ]] || return 1

  local enc_group
  enc_group="$(urlenc "$group")"

  api_put "/proxies/${enc_group}" "$(jq -nc --arg name "$node" '{name:$name}')" >/dev/null
  info "Switched group [$group] -> [$node]"
}

has_mihomo_cap_net_admin() {
  command -v getcap >/dev/null 2>&1 || return 1
  getcap "$MIHOMO_BIN" 2>/dev/null | grep -q 'cap_net_admin'
}

disable_dns_fallback_geoip() {
  local cfg="$1"
  [[ -f "$cfg" ]] || return 1
  yq -y -i '
    .dns.enable = false |
    .dns.fallback = [] |
    .dns."fallback-filter" = null
  ' "$cfg"
}
#===============测试小工具，上======================================


#==============================配置文件读取，下======================================
get_main_group() {
  local pjson
  pjson="$(api_get "/proxies" 2>/dev/null)" || return 1

  # 1) prefer group named "Proxy"
  if jq -e '.proxies["Proxy"]? != null and .proxies["Proxy"].all? != null' >/dev/null <<<"$pjson"; then
    echo "Proxy"
    return 0
  fi

  # 2) fallback to your heuristic
  jq -r '
    .proxies
    | to_entries
    | map(select(.value.all? != null))
    | (map(select(.value.type?=="Selector" or .value.type?=="URLTest" or .value.type?=="Fallback" or .value.type?=="LoadBalance")) + .)
    | .[0].key // empty
  ' <<<"$pjson"
}

get_now_node() {
  # 输出“当前节点名”；失败输出空并返回非 0
  local group pjson
  group="$(get_main_group)" || return 1
  [[ -n "$group" ]] || return 1

  pjson="$(api_get "/proxies" 2>/dev/null)" || return 1
  jq -r --arg g "$group" '.proxies[$g].now // empty' <<<"$pjson"
}


#==============================配置文件读取，上======================================

#============================测速、测速结果处理，下=================================

speedtest_node_delay() {
  # 用 mihomo 内置 delay API 测某个节点，输出 ms 或 "-"
  # 用法：speedtest_node_delay "<node_name>" [timeout_ms]
  local node="${1:-}"
  local timeout_ms="${2:-1000}"
  [[ -n "$node" ]] || { echo "-"; return 1; }

  # DIRECT/REJECT 没必要测
  if [[ "$node" == "DIRECT" || "$node" == "REJECT" ]]; then
    echo "-"
    return 0
  fi
  api_delay "$node" "$timeout_ms"
}
speedtest_now() {
  # 用法：speedtest_now [timeout_ms]
  local timeout_ms="${1:-1000}"
  local node delay
  node="$(get_now_node || true)"
  if [[ -z "$node" ]]; then
    echo "⚠️  无法获取当前节点（mihomo API 不通或没有可用 group）"
    return 1
  fi

  delay="$(speedtest_node_delay "$node" "$timeout_ms")"
  # 简单打个标
  local badge="🟡"
  if [[ "$delay" =~ ^[0-9]+$ ]]; then
    if (( delay <= 150 )); then badge="🟢"
    elif (( delay <= 400 )); then badge="🟡"
    else badge="🔴"
    fi
  else
    badge="⚪"
  fi
  echo "👉 当前节点：${node}"
  echo "${badge} 延迟：${delay} ms  (timeout=${timeout_ms})"
}
wait_api() {
  local t="${1:-5}"  # seconds
  local i=0
  while (( i < t*10 )); do
    if api_get "/proxies" >/dev/null 2>&1; then
      return 0
    fi
    sleep 0.1
    i=$((i+1))
  done
  return 1
}
#============================测速、测速结果处理，上=================================

#============================mihomo配置，下==================
write_cfg() { # name url file 
  local name="$1"
  local url="$2"

  cat >  "${MIHOMO_ROOT}/$name/config.yaml" <<YAML # 直接使用这个即可
mixed-port: ${MIXED_PORT}
socks-port: ${SOCK_PORT}

allow-lan: false
bind-address: ${API_HOST}
mode: rule
log-level: error

external-controller: ${API_HOST}:${API_PORT}
secret: ""

proxy-providers:
  ${name}:
    type: http
    url: "${url}"
    interval: 86400
    path: "${MIHOMO_ROOT}/$name/providers/$name.yaml"

proxy-groups:
  - name: Proxy
    type: select
    use:
      - ${name}
  - name: Final
    type: select
    proxies:
      - Proxy
      - DIRECT

rules:
  - DOMAIN-SUFFIX,localhost,DIRECT
  - DOMAIN-SUFFIX,local,DIRECT
  - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
  - IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
  - IP-CIDR,224.0.0.0/4,DIRECT,no-resolve
  - IP-CIDR6,::1/128,DIRECT,no-resolve
  - IP-CIDR6,fc00::/7,DIRECT,no-resolve
  - IP-CIDR6,fe80::/10,DIRECT,no-resolve
  - MATCH,Final
YAML
}

ensure_profile_config() {
  local profile_dir="$1"
  local cfg="${profile_dir}/config.yaml"
  [[ -d "$profile_dir" ]] || die "profile not found: $profile_dir"
  [[ -f "$cfg" ]] || die "missing config: $cfg"

  mkdir -p "${profile_dir}/providers"

  local -a providers
  mapfile -t providers < <(yq -r '."proxy-providers" | keys[]?' "$cfg" 2>/dev/null)
  (( ${#providers[@]} > 0 )) || die "missing proxy-providers in $cfg"

  local provider ptype purl ppath
  for provider in "${providers[@]}"; do
    ptype="$(yq -r --arg p "$provider" '."proxy-providers"[$p].type // ""' "$cfg")"
    case "$ptype" in
      http)
        purl="$(yq -r --arg p "$provider" '."proxy-providers"[$p].url // ""' "$cfg")"
        [[ -n "$purl" ]] || die "provider [$provider] is http but url is empty"
        yq -y -i --arg p "$provider" --arg path "${profile_dir}/providers/${provider}.yaml" '."proxy-providers"[$p].path = $path | ."proxy-providers"[$p].interval = 86400' "$cfg"
        ;;
      file)
        ppath="$(yq -r --arg p "$provider" '."proxy-providers"[$p].path // ""' "$cfg")"
        [[ -n "$ppath" ]] || ppath="${profile_dir}/providers/${provider}.yaml"
        yq -y -i --arg p "$provider" --arg path "$ppath" '."proxy-providers"[$p].path = $path' "$cfg"
        [[ -f "$ppath" ]] || die "provider [$provider] file not found: $ppath"
        ;;
      *)
        die "provider [$provider] has unsupported or empty type: ${ptype:-<empty>}"
        ;;
    esac
  done

  local mode
  mode="$(yq -r '.mode // ""' "$cfg" 2>/dev/null)"
  case "$mode" in
    rule|global|direct) ;;
    "") mode="rule" ;;
    *) warn "invalid mode [$mode], reset to rule"; mode="rule" ;;
  esac

  yq -y -i "
    .mode = \"${mode}\" |
    .[\"mixed-port\"] = ${MIXED_PORT} |
    .[\"socks-port\"] = ${SOCK_PORT} |
    .[\"external-controller\"] = \"${API_HOST}:${API_PORT}\" |
    .secret = (.secret // \"\") |
    .[\"log-level\"] = \"error\" |
    .[\"allow-lan\"] = (.[\"allow-lan\"] // false) |
    .[\"bind-address\"] = \"${API_HOST}\"
  " "$cfg"

  if ! yq -e '([."proxy-groups"[]?.name] | index("Proxy") != null and index("Final") != null) and (.rules | type == "array" and length > 0)' "$cfg" >/dev/null 2>&1; then
    warn "proxy-groups or rules missing, applying basic rule config"
    reset_basic_rules "$cfg" || die "failed to initialize basic rules"
  fi

  yq -e '.mode as $mode | ($mode == "rule" or $mode == "global" or $mode == "direct") and (."mixed-port" | type == "number") and (."socks-port" | type == "number") and (."proxy-providers" | type == "object" and length > 0) and (."proxy-groups" | type == "array" and length > 0) and (.rules | type == "array" and length > 0)' "$cfg" >/dev/null || die "invalid mihomo config after normalization: $cfg"
}
#=====================mihomo配置，上====================================
print_mihomo_status() {
  local cfg pjson
  cfg="$(api_get "/configs" 2>/dev/null)" || { echo "❌ API 不通: ${API_BASE}"; return 1; }
  pjson="$(api_get "/proxies" 2>/dev/null)" || { echo "❌ 读取 /proxies 失败"; return 1; }

  local allow_lan bind_addr mode mixed_port ec
  allow_lan="$(jq -r '."allow-lan" // false' <<<"$cfg")"
  bind_addr="$(jq -r '."bind-address" // "-"' <<<"$cfg")"
  mode="$(jq -r '.mode // "-"' <<<"$cfg")"
  mixed_port="$(jq -r '."mixed-port" // .port // "-"' <<<"$cfg")"
  ec="$(jq -r '."external-controller" // "-"' <<<"$cfg")"

  local group now node_count
  group="$(get_main_group 2>/dev/null || true)"
  [[ -n "$group" ]] || { echo "❌ 找不到主组（/proxies 里没有可用 group）"; return 1; }

  now="$(jq -r --arg g "$group" '.proxies[$g].now // "-"' <<<"$pjson")"
  node_count="$(jq -r --arg g "$group" '.proxies[$g].all | length' <<<"$pjson" 2>/dev/null || echo "-")"

  local lan_icon="🔒"
  [[ "$allow_lan" == "true" ]] && lan_icon="📡"

  echo "🧠 Mihomo Status"
  echo "🌐 API:   ${API_BASE}  (EC=${ec})"
  echo "🚪 Port:  mixed-port=${mixed_port}"
  echo "${lan_icon} LAN:   allow-lan=${allow_lan}  bind=${bind_addr}"
  echo "🧭 Mode:  ${mode}"
  echo "🧩 Group: ${group}"
  echo "📦 Nodes: ${node_count}"
  echo "✅ Now:   ${now}"
}

port_open_local() {
  local port="$1"
  ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "(:|\\.)${port}\$"
}

show_runtime_routing_status() {
  local cfg pjson
  cfg="$(api_get "/configs" 2>/dev/null)" || {
    echo "Routing: API unavailable"
    return 0
  }
  pjson="$(api_get "/proxies" 2>/dev/null)" || {
    echo "Routing: /proxies unavailable"
    return 0
  }

  local mode global_now final_now proxy_now
  mode="$(jq -r '.mode // "-"' <<<"$cfg")"
  global_now="$(jq -r '.proxies["GLOBAL"].now // empty' <<<"$pjson")"
  final_now="$(jq -r '.proxies["Final"].now // empty' <<<"$pjson")"
  proxy_now="$(jq -r '.proxies["Proxy"].now // empty' <<<"$pjson")"

  echo "Mode: ${mode}"
  [[ -n "$global_now" ]] && echo "GLOBAL -> ${global_now}"
  [[ -n "$final_now" ]] && echo "Final  -> ${final_now}"
  [[ -n "$proxy_now" ]] && echo "Proxy  -> ${proxy_now}"
}

extract_proxy_port() {
  local value="${1:-}"
  [[ -n "$value" ]] || return 1
  sed -E 's#^[[:alpha:]][[:alnum:]+.-]*://[^:/]+:([0-9]+)/*$#\1#' <<<"$value"
}

proxy_env_status_line() {
  local name="$1"
  local value="$2"

  if [[ -z "$value" ]]; then
    echo "${name}: OFF"
    return 0
  fi

  local port
  port="$(extract_proxy_port "$value" 2>/dev/null || true)"
  if [[ -n "$port" ]] && port_open_local "$port"; then
    echo "${name}: ON  (${value}, port ${port} reachable)"
  elif [[ -n "$port" ]]; then
    echo "${name}: STALE  (${value}, port ${port} not listening)"
  else
    echo "${name}: ON  (${value})"
  fi
}



start_end(){  # start/end name/none 文件
    local start0Rend="${1:-}"
    local name_file=${2:-}
    local cfg=$MIHOMO_ROOT/$name_file
    [[ -n "$start0Rend" ]] || die "usage: $0 start|end"
    if [[ "$start0Rend" == "start" ]]; then
        start_end "end" # 先停止当前
        [[ -d "$cfg" ]] || die "profile not found: $cfg"
        [[ -f "$cfg/config.yaml" ]] || die "missing config: $cfg/config.yaml"
        ensure_profile_config "$cfg"
        if [[ "$(yq -r '.tun.enable // false' "$cfg/config.yaml" 2>/dev/null || echo false)" != "true" ]]; then
          disable_dns_fallback_geoip "$cfg/config.yaml"
        fi
        # 进入的前提配置没有问题，可以直接运行
        info "start mihomo with config=$cfg/config.yaml data=$cfg"
        # 前台跑不方便，这里后台启动并记录 pid
        test_port "$API_PORT" "$MIXED_PORT" "$SOCK_PORT"
        "$MIHOMO_BIN" -d "$cfg" -f "$cfg/config.yaml" >"$cfg/mihomo.log" 2>&1 & # 每次启动情况记录
        echo $! >"$PID_FILE" # 保存到定点位置
    elif [[ "$start0Rend" == "end" ]]; then
        if [[ -f "$PID_FILE" ]]; then # 存在就终止
            local pid
            pid="$(cat "$PID_FILE" || true)"
            if [[ -n "${pid}" ]] && kill "$pid" 2>/dev/null; then
              info "stop process  pid=$pid"
              # 等一下让它退出
              for _ in {1..20}; do
                  kill -0 "$pid" 2>/dev/null || break
                  sleep 0.1
              done
              if kill -0 "$pid" 2>/dev/null; then
                warn "force kill pid=$pid"
                kill -9 "$pid" 2>/dev/null || true
              fi
            fi
            rm -f "$PID_FILE"
        fi
    else
        die "usage: $0 start|end"
    fi
}

cfg_add() { # add name src
  if [[ "$1" == "add" ]]; then
    shift # 去掉 "add" 参数
  fi
  local name="${1:-}"
  local src="${2:-}"
  local cfg_root="${MIHOMO_ROOT}/${name}"
  [[ -n "$name" ]] || die "usage: $0 add <name> <url|file>"
  [[ -n "$src" ]]  || die "usage: $0 add <name> <url|file>"
  if [[ -d "$cfg_root" ]]; then
    die "profile exists: $cfg_root ,请换一个名称"
  fi
  mkdir -p "$cfg_root/providers"
  write_cfg "$name" "$src" 
  if [[ -f "$src" ]]; then #file: 直接复制,不用下载了
    info "copy file -> ${cfg_root}/providers/$name.yaml"
    cp -f "$src" "$cfg_root/providers/$name.yaml"
    yq -y -i "
      .[\"proxy-providers\"][\"$name\"].type = \"file\" |
      del(.[\"proxy-providers\"][\"$name\"].url) |
      del(.[\"proxy-providers\"][\"$name\"].interval)
    " "$cfg_root/config.yaml"
    info "saved: ${cfg_root}/providers/$name.yaml" # 有了文件，等会挂载
  fi
    ensure_bypass_list "$cfg_root"
    reset_basic_rules "$cfg_root/config.yaml"
    # 启动这个配置
    select_one "select" "$name" # 启动这个新的节点
    print_mihomo_status 1000
}

get_group_nodes_json() {
  local pjson group nodes
  pjson="$(api_get "/proxies")" || return 1

  group="$(get_main_group)" || return 1
  [[ -n "$group" ]] || return 1

  nodes="$(jq -c --arg g "$group" '.proxies[$g].all' <<<"$pjson")" || return 1
  jq -nc --arg group "$group" --argjson nodes "$nodes" '{group:$group, nodes:$nodes}'
}


provider_http_list() {
  local cfg="$1"
  yq -r '."proxy-providers" | to_entries[]? | select(.value.type == "http") | .key' "$cfg"
}

provider_in_list() {
  local needle="$1"
  shift
  local item
  for item in "$@"; do
    [[ "$item" == "$needle" ]] && return 0
  done
  return 1
}

update_one_provider() {
  local provider="$1"
  local enc_provider
  enc_provider="$(urlenc "$provider")"
  api_put_empty "/providers/proxies/${enc_provider}" >/dev/null
  info "updated provider: $provider"
}

update_providers_cmd() {
  local target="${1:-}"
  local prof cfg_dir cfg
  prof="$(current_profile)" || die "no current profile"
  cfg_dir="${MIHOMO_ROOT}/${prof}"
  cfg="${cfg_dir}/config.yaml"
  ensure_profile_config "$cfg_dir"

  local -a providers selected
  mapfile -t providers < <(provider_http_list "$cfg")
  (( ${#providers[@]} > 0 )) || die "no http proxy-providers in profile=${prof}"

  if ! wait_api 3; then
    info "mihomo API not ready, starting current profile=${prof} first..."
    start_end start "$prof"
    wait_api 5 || die "mihomo API not ready after start"
  fi

  case "$target" in
    "" )
      echo "📦 HTTP providers (profile=${prof}):"
      echo "  0) ALL"
      local i pick
      for i in "${!providers[@]}"; do
        printf "  %d) %s\n" "$((i+1))" "${providers[$i]}"
      done
      while true; do
        read -r -p "👉 请输入序号 (0-${#providers[@]}): " pick
        [[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; }
        (( pick>=0 && pick<=${#providers[@]} )) || { echo "❌ 超出范围"; continue; }
        break
      done
      if (( pick == 0 )); then
        selected=("${providers[@]}")
      else
        selected=("${providers[$((pick-1))]}")
      fi
      ;;
    all|ALL)
      selected=("${providers[@]}")
      ;;
    *)
      provider_in_list "$target" "${providers[@]}" || die "provider not found or not http: $target"
      selected=("$target")
      ;;
  esac

  local provider
  for provider in "${selected[@]}"; do
    update_one_provider "$provider"
  done
}

theme_cmd() {
  local action="${1:-show}"
  case "$action" in
    show|current)
      echo "current theme: $(theme_current)"
      echo "available: basic ganyu kaomoji minimal"
      ;;
    list)
      cat <<THEMES
basic    - 稳定基础版，ASCII 前缀，最不挑终端
ganyu    - 原神甘雨风格，少量 emoji + 轻提示
kaomoji  - 颜文字版，适合 emoji 显示不完整的终端
minimal  - 极简版，不加额外表情
THEMES
      ;;
    basic|ganyu|kaomoji|minimal)
      printf '%s\n' "$action" > "$THEME_FILE"
      info "theme set to ${action}"
      ;;
    *)
      die "usage: $0 theme [show|list|basic|ganyu|kaomoji|minimal]"
      ;;
  esac
}

help() {
  cat <<EOF
Usage:
  mh [command] [args...]

Commands:
  add <name> <url|file>     新建 profile
  select [name]             切换/启动 profile，不带 name 时交互选择
  start <name>              等价于 select <name>
  end                       停止当前 mihomo
  env                       输出代理 export，常用：eval "\$(mh env)"
  theme [name]              查看或切换输出主题
  sysproxy [on|off|status]  开关 GNOME 系统代理
  update [all|provider]     更新当前 profile 的 HTTP provider
  mode rule|global|direct   切换运行模式并重启
  rules show|reset          查看或重置基础规则,更新 bypass.list
  tun [on|off|toggle]       开关 TUN
  list | -l | --list [kw]   列出节点并可输入序号切换
  doctor                    输出诊断信息
  db                        调试菜单
  help | -h | --help        显示帮助

Default:
  mh                        显示 TUN/路由/代理环境/当前节点延迟
  mh <keyword>              搜索节点并按延迟排序选择切换

Examples:
  mh add demo "https://example.com/sub.yaml"
  mh add local "/path/to/sub.yaml"
  mh select
  mh select demo
  mh 美国
  eval "\$(mh env)"

Notes:
  root=${MIHOMO_ROOT}
  api=${API_BASE}
  mixed-port=${MIXED_PORT}
  mihomo=${MIHOMO_BIN}
EOF
}


select_one(){ # select name
  shift 
  local name
  if [[ -z "${1:-}" ]];then
    local -a files
    mapfile -t files < <(find "$MIHOMO_ROOT" -mindepth 1 -maxdepth 1 -type d  -exec test -f '{}/config.yaml' ';' -print | sort)
    if (( ${#files[@]} == 0 )); then
      echo "⚠️  ${MIHOMO_ROOT} 下没有找到任何 目录"
      return 1
    fi
    echo "📄 可用配置（输入序号选择，例如 1）："
    local i
    for i in "${!files[@]}"; do
      printf "  %d) %s\n" "$((i+1))" "$(basename "${files[$i]}")"
    done

    local pick 
    while true; do
      read -r -p "👉 请输入序号 (1-${#files[@]}): " pick
      [[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; }
      (( pick>=1 && pick<=${#files[@]} )) || { echo "❌ 超出范围"; continue; }

      name="$(basename "${files[$((pick-1))]}")"
      break
    done
  else
   local file_cfg="$MIHOMO_ROOT/$1"
   if [[ -d $file_cfg ]];then
    name="$1"
   else
    echo "❌ 不存在: $file_cfg"# 输入name有无
    return 1
   fi
  fi
    echo "$name" > "${MIHOMO_ROOT}/current.profile"
    start_end "start" "$name"
    wait_api 5 || warn "mihomo API not ready yet"
    sync_global_group || warn "failed to sync GLOBAL -> Proxy"
    speedtest_now 1000

}

#====================tun控制，下=========================

current_profile() {
  [[ -f "${MIHOMO_ROOT}/current.profile" ]] || return 1
  cat "${MIHOMO_ROOT}/current.profile"
}

ensure_bypass_list() {
  local profile_dir="$1"
  local file="${profile_dir}/bypass.list"
  [[ -f "$file" ]] && return 0
  cat > "$file" <<BYPASS
*zhihu.com
*zhimg.com
*jd.com
100ime-iat-api.xfyun.cn
*360buyimg.com
localhost
*.local
127.*
10.*
172.*
192.168.*
*.pcl.ac.cn
BYPASS
}

compile_bypass_rules() {
  local file="$1"
  local line value prefix
  [[ -f "$file" ]] || return 0

  while IFS= read -r line || [[ -n "$line" ]]; do
    line="${line%%#*}"
    line="$(printf "%s" "$line" | sed -E "s/^[[:space:]]+|[[:space:]]+$//g")"
    [[ -z "$line" ]] && continue

    case "$line" in
      */*)
        printf "%s\n" "IP-CIDR,${line},DIRECT,no-resolve"
        ;;
      [0-9]*.*\*)
        prefix="${line%%.*}"
        case "$prefix" in
          10) printf "%s\n" "IP-CIDR,10.0.0.0/8,DIRECT,no-resolve" ;;
          127) printf "%s\n" "IP-CIDR,127.0.0.0/8,DIRECT,no-resolve" ;;
          172) printf "%s\n" "IP-CIDR,172.16.0.0/12,DIRECT,no-resolve" ;;
          192) printf "%s\n" "IP-CIDR,192.168.0.0/16,DIRECT,no-resolve" ;;
          *) warn "unsupported bypass IP wildcard: $line" ;;
        esac
        ;;
      [0-9]*.[0-9]*.[0-9]*.[0-9]*)
        printf "%s\n" "IP-CIDR,${line}/32,DIRECT,no-resolve"
        ;;
      \**)
        value="${line#\*}"
        value="${value#.}"
        [[ -n "$value" ]] && printf "%s\n" "DOMAIN-SUFFIX,${value},DIRECT"
        ;;
      .*)
        value="${line#.}"
        [[ -n "$value" ]] && printf "%s\n" "DOMAIN-SUFFIX,${value},DIRECT"
        ;;
      *)
        printf "%s\n" "DOMAIN,${line},DIRECT"
        ;;
    esac
  done < "$file" | awk "!seen[\$0]++"
}

rules_json_from_lines() {
  jq -R -s -c "split(\"\n\") | map(select(. != \"\"))"
}

reset_basic_rules() {
  local cfg="$1"
  [[ -f "$cfg" ]] || return 1

  local providers_json profile_dir bypass_rules rules_json
  providers_json="$(yq -c '."proxy-providers" | keys' "$cfg" 2>/dev/null)"
  [[ "$providers_json" != "null" && "$providers_json" != "[]" ]] || { warn "cannot detect proxy-provider names from $cfg"; return 1; }

  profile_dir="$(dirname "$cfg")"
  ensure_bypass_list "$profile_dir"
  bypass_rules="$(compile_bypass_rules "${profile_dir}/bypass.list")"
  rules_json="$({
    printf "%s\n" "$bypass_rules"
    printf "%s\n" \
      "IP-CIDR,100.64.0.0/10,DIRECT,no-resolve" \
      "IP-CIDR,224.0.0.0/4,DIRECT,no-resolve" \
      "IP-CIDR6,::1/128,DIRECT,no-resolve" \
      "IP-CIDR6,fc00::/7,DIRECT,no-resolve" \
      "IP-CIDR6,fe80::/10,DIRECT,no-resolve" \
      "MATCH,Final"
  } | rules_json_from_lines)"

  yq -y -i --argjson providers "$providers_json" --argjson rules "$rules_json" ".mode = \"rule\" | .[\"proxy-groups\"] = [{\"name\":\"Proxy\",\"type\":\"select\",\"use\":\$providers},{\"name\":\"Final\",\"type\":\"select\",\"proxies\":[\"Proxy\",\"DIRECT\"]}] | .rules = \$rules" "$cfg"
}

mode_set() {
  local next_mode="${1:-}"
  case "$next_mode" in
    rule|global|direct) ;;
    *) die "usage: $0 mode rule|global|direct" ;;
  esac

  local prof cfg
  prof="$(current_profile)" || die "no current profile"
  cfg="${MIHOMO_ROOT}/${prof}/config.yaml"
  [[ -f "$cfg" ]] || die "missing config: $cfg"

  if [[ "$next_mode" == "rule" ]] && ! yq -e '.rules | length > 0' "$cfg" >/dev/null 2>&1; then
    info "No rules found in profile=${prof}, applying basic rules first..."
    reset_basic_rules "$cfg" || die "failed to initialize basic rules"
  fi

  yq -y -i ".mode = \"${next_mode}\"" "$cfg"
  info "Mode set to ${next_mode} (profile=${prof}), restarting..."
  start_end end
  start_end start "$prof"
  wait_api 5 || warn "mihomo API not ready"
  if [[ "$next_mode" == "global" ]]; then
    sync_global_group || warn "failed to sync GLOBAL -> Proxy"
  fi
  print_mihomo_status || true
}

rules_cmd() {
  local action="${1:-show}"
  local cfg prof
  prof="$(current_profile)" || die "no current profile"
  cfg="${MIHOMO_ROOT}/${prof}/config.yaml"
  [[ -f "$cfg" ]] || die "missing config: $cfg"

  case "$action" in
    show)
      echo "Rules (profile=${prof}):"
      yq -r '.rules[]?' "$cfg"
      ;;
    reset)
      reset_basic_rules "$cfg" || die "failed to reset basic rules"
      info "Basic rules reset (profile=${prof}), restarting..."
      start_end end
      start_end start "$prof"
      wait_api 5 || warn "mihomo API not ready"
      print_mihomo_status || true
      ;;
    *)
      die "usage: $0 rules show|reset"
      ;;
  esac
}

tun() {
  # 用法：sudo mh tun on|off|toggle
  local action="${1:-toggle}"
  local prof
  prof="$(current_profile)" || { warn "no current profile"; return 1; }

  local cfg="${MIHOMO_ROOT}/${prof}/config.yaml"
  [[ -f "$cfg" ]] || { warn "missing config: $cfg"; return 1; }

  local cur
  cur="$(yq -r '.tun.enable // false' "$cfg" 2>/dev/null || echo false)"

  local next
  case "$action" in
    on) next=true ;;
    off) next=false ;;
    toggle) [[ "$cur" == "true" ]] && next=false || next=true ;;
    *) warn "usage: mh tun on|off|toggle"; return 1 ;;
  esac
  if [[ "$next" == "true" && "${EUID:-$(id -u)}" -ne 0 ]] && ! has_mihomo_cap_net_admin; then
    warn "开启 TUN 通常需要 root，或提前给 mihomo 设置 cap_net_admin/cap_net_raw"
    warn "继续尝试启动；若失败，请执行：sudo setcap cap_net_admin,cap_net_raw+ep \"$MIHOMO_BIN\""
  fi
  yq -y -i "
    .tun.enable = ${next} |
    .tun.stack = (.tun.stack // \"system\") |
    .tun.device = (.tun.device // \"mihomo\") |
    .tun[\"auto-route\"] = (.tun[\"auto-route\"] // true) |
    .tun[\"auto-redir\"] = (.tun[\"auto-redir\"] // true) |
    .tun[\"auto-detect-interface\"] = (.tun[\"auto-detect-interface\"] // true) |
    .tun[\"dns-hijack\"] = (.tun[\"dns-hijack\"] // []) |
    ( .tun[\"dns-hijack\"] |= ( . + [\"any:53\"] | unique ) ) |

    .dns.enable = ${next} |
    .dns.listen = (.dns.listen // \"127.0.0.1:1053\") |
    .dns[\"enhanced-mode\"] = \"redir-host\" |
    .dns.nameserver = (.dns.nameserver // [\"223.5.5.5\",\"119.29.29.29\"]) |
    .dns.fallback = (.dns.fallback // [\"1.1.1.1\",\"8.8.8.8\"]) |
    .dns[\"fallback-filter\"].geoip = false
  " "$cfg"

  if [[ "$next" != "true" ]]; then
    disable_dns_fallback_geoip "$cfg"
  fi

  info "TUN set to ${next} (profile=${prof}), restarting..."
  start_end end
  start_end start "$prof"
  wait_api 5 || warn "mihomo API not ready"
  sync_global_group || warn "failed to sync GLOBAL -> Proxy"
  print_mihomo_status 1200 || true
}

#=============================tun控制，上===================

list_nodes() {
  local kw="${1:-}"
  wait_api 5 || { warn "mihomo API not ready"; return 1; }

  local pjson group now
  pjson="$(api_get "/proxies")" || { warn "cannot read /proxies"; return 1; }

  group="$(get_main_group)" || { warn "cannot detect main group"; return 1; }
  now="$(jq -r --arg g "$group" '.proxies[$g].now // ""' <<<"$pjson")"

  local -a nodes
  if [[ -n "$kw" ]]; then
    mapfile -t nodes < <(jq -r --arg g "$group" --arg kw "$kw" '
      def lc: ascii_downcase;
      .proxies[$g].all
      | map(select((contains($kw)) or ((lc) | contains($kw | lc))))
      | .[]
    ' <<<"$pjson")
  else
    mapfile -t nodes < <(jq -r --arg g "$group" '.proxies[$g].all[]' <<<"$pjson")
  fi

  (( ${#nodes[@]} > 0 )) || { warn "No nodes matched: ${kw:-<all>}"; return 1; }

  echo "📌 Group: ${group}"
  [[ -n "$now" ]] && echo "⭐ Now:   ${now}"
  [[ -n "$kw" ]] && echo "🔎 Filter: ${kw}"
  echo

  local i
  for i in "${!nodes[@]}"; do
    printf "  %d) %s%s\n" "$((i+1))" "$(if [[ "${nodes[$i]}" == "$now" ]]; then printf "⭐ "; else printf "   "; fi)" "${nodes[$i]}"
  done

  [[ -t 0 ]] || return 0
  local pick
  while true; do
    read -r -p "👉 选择节点 (1-${#nodes[@]}, 0 cancel): " pick
    [[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; }
    (( pick == 0 )) && { echo "cancelled"; return 0; }
    (( pick>=1 && pick<=${#nodes[@]} )) || { echo "❌ 超出范围"; continue; }
    break
  done

  switch_group_node "$group" "${nodes[$((pick-1))]}"
  speedtest_now 1500 || true
}

#===============================搜索测速排序，下======================
# 批量测延迟（默认并发 6），输出："<delay>\t<node>"
batch_delay() {
  # 用法：batch_delay <timeout_ms> <max_jobs> <node1> <node2> ...
  local timeout_ms="${1:-1200}"; shift || true
  local max_jobs="${1:-6}"; shift || true
  local -a nodes=( "$@" )

  # 兼容：没有节点直接返回
  (( ${#nodes[@]} == 0 )) && return 0

  # 并发测
  local tmp
  tmp="$(mktemp)"
  rm -f "$tmp"; : >"$tmp"

  # 简单并发控制：后台跑，超过 max_jobs 就 wait 一个
  local running=0
  for n in "${nodes[@]}"; do
    (
      local d
      d="$(api_delay "$n" "$timeout_ms")"
      # 统一成数值："-" 变成 999999，便于排序
      if [[ "$d" =~ ^[0-9]+$ ]]; then
        printf "%s\t%s\n" "$d" "$n"
      else
        printf "999999\t%s\n" "$n"
      fi
    ) >>"$tmp" &
    running=$((running+1))
    if (( running >= max_jobs )); then
      wait -n 2>/dev/null || wait
      running=$((running-1))
    fi
  done
  wait

  # 排序输出
  sort -n "$tmp"
  rm -f "$tmp"
}

# 关键词匹配 + 测速排序 + 选择切换
pick_switch_with_delay() {
  # 用法：pick_switch_with_delay "<keyword>" [timeout_ms]
  local kw="${1:-}"
  local timeout_ms="${2:-1200}"
  [[ -n "$kw" ]] || { warn "usage: mh <keyword>"; return 1; }

  wait_api 5 || { warn "mihomo API not ready"; return 1; }

  local gj group
  gj="$(get_group_nodes_json)" || { warn "cannot read proxies/groups"; return 1; }
  group="$(jq -r '.group' <<<"$gj")"

  # 匹配候选：中文用 contains；英文用 lower-case contains
  local candidates
  candidates="$(jq -r --arg kw "$kw" '
    def lc: ascii_downcase;
    .nodes
    | map(select(. != "DIRECT" and . != "REJECT"))
    | map(select(
        (contains($kw)) or ((lc) | contains($kw | lc))
      ))
    | .[]
  ' <<<"$gj")"

  if [[ -z "$candidates" ]]; then
    warn "No nodes matched keyword: $kw"
    info "Tip: try: 美国 / 香港 / 日本 / 狮城 / 韩国 / 台湾 / US / HK / JP / SG"
    return 1
  fi

  mapfile -t arr <<<"$candidates"

  info "Testing delay for ${#arr[@]} nodes (timeout=${timeout_ms}ms)..."
  # 并发 6：你可以改大/改小
  local lines
  lines="$(batch_delay "$timeout_ms" 6 "${arr[@]}")"

  # 组装排序后的节点数组（按 delay asc）
  local -a sorted_nodes=()
  local -a sorted_delay=()
  while IFS=$'\t' read -r d n; do
    sorted_nodes+=( "$n" )
    sorted_delay+=( "$d" )
  done <<<"$lines"

  echo "🔎 Matched nodes in group [$group] for keyword: [$kw]  (sorted by delay)"
  local i showd
  for i in "${!sorted_nodes[@]}"; do
    showd="${sorted_delay[$i]}"
    [[ "$showd" == "999999" ]] && showd="-"   # 显示时还原
    printf "  %d) %s\t%s ms\n" "$((i+1))" "${sorted_nodes[$i]}" "$showd"
  done

  local pick
  while true; do
    read -r -p "👉 choose (1-${#sorted_nodes[@]}, 0 cancel): " pick
    [[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; }
    (( pick==0 )) && { echo "cancelled"; return 0; }
    (( pick>=1 && pick<=${#sorted_nodes[@]} )) || { echo "❌ 超出范围"; continue; }
    break
  done

  switch_group_node "$group" "${sorted_nodes[$((pick-1))]}"
  speedtest_now 1500 || true
}



#==============================搜索测速排序，上======================

#============================状态检查，下=====================
tun_status() {
  local prof cfg enabled dev
  prof="$(current_profile 2>/dev/null || true)"
  [[ -n "$prof" ]] || { echo "TUN: unknown (no current profile)"; return 0; }

  cfg="${MIHOMO_ROOT}/${prof}/config.yaml"
  [[ -f "$cfg" ]] || { echo "TUN: unknown (missing config)"; return 0; }

  enabled="$(yq -r '.tun.enable // false' "$cfg" 2>/dev/null || echo false)"
  dev="$(yq -r '.tun.device // "mihomo"' "$cfg" 2>/dev/null || echo mihomo)"

  if [[ "$enabled" != "true" ]]; then
    echo "TUN: OFF (profile=${prof})"
    return 0
  fi

  echo "TUN: ON (profile=${prof})"
  echo "Expect iface: ${dev}"

  if ip link show "$dev" >/dev/null 2>&1; then
    echo "Iface: ✅ ${dev} exists"
  else
    echo "Iface: ❌ ${dev} not found (mihomo tun may not be running/privileged)"
  fi

  echo "Default route:"
  ip route show default 2>/dev/null | sed 's/^/  /' || true
}



proxy_env_status() {
  proxy_env_status_line "HTTP_PROXY" "${HTTP_PROXY:-}"
  proxy_env_status_line "HTTPS_PROXY" "${HTTPS_PROXY:-}"
  proxy_env_status_line "ALL_PROXY" "${ALL_PROXY:-}"
}

show_proxy_exports() {
  cat <<EOF
export http_proxy="http://127.0.0.1:${MIXED_PORT}"
export https_proxy="http://127.0.0.1:${MIXED_PORT}"
export all_proxy="socks5h://127.0.0.1:${MIXED_PORT}"

export HTTP_PROXY="\$http_proxy"
export HTTPS_PROXY="\$https_proxy"
export ALL_PROXY="\$all_proxy"

export NO_PROXY="127.0.0.1,localhost,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
export no_proxy="\$NO_PROXY"
EOF
}

net_test() {
  # 常用网站连通性测试：输出 “URL -> OK/FAIL”
  local -a urls=(
    "https://www.google.com"
    "https://www.cloudflare.com"
    "https://www.github.com"
    "https://www.youtube.com"
    "https://www.baidu.com"
    "https://www.bing.com"
  )

  echo "== Net Test (timeout=10s) =="
  local url
  for url in "${urls[@]}"; do
    if curl -fsS -I -m 10 "$url" >/dev/null 2>&1; then
      printf "%s -> ✅ OK\n" "$url"
    else
      printf "%s -> ❌ FAIL\n" "$url"
    fi
  done
}

net_test_double() {
  local -a urls=(
    "https://www.google.com"
    "https://www.cloudflare.com"
    "https://www.github.com"
    "https://www.youtube.com"
    "https://www.baidu.com"
    "https://www.bing.com"
  )

  echo "== Net Test (timeout=10s) =="
  echo "Format: URL -> DIRECT | PROXY(mixed-port=${MIXED_PORT})"
  local url ok1 ok2

  for url in "${urls[@]}"; do
    # 直连（完全忽略环境代理）
    if curl -fsS -I -m 10 --noproxy '*' "$url" >/dev/null 2>&1; then
      ok1="✅"
    else
      ok1="❌"
    fi

    # 强制走 mihomo mixed-port（明确指定代理）
    if curl -fsS -I -m 10 -x "http://127.0.0.1:${MIXED_PORT}" "$url" >/dev/null 2>&1; then
      ok2="✅"
    else
      ok2="❌"
    fi

    printf "%-28s -> DIRECT:%s  PROXY:%s\n" "$url" "$ok1" "$ok2"
  done
}


#============================状态检查，上=====================

#=========================debug,下===============================
debug_one() {
  shift

  local -a debug_code=()
  debug_code[1]="ss -lntp | grep -E ':(9090|9091|7890|7891)'|| echo \"❌ 未找到代理服务\""
  debug_code[2]="ps -fp \${1:-} -o pid,ppid,cmd"
  debug_code[3]="net_test"
  debug_code[4]="net_test_double"
  debug_code[5]="test_env"
  debug_code[6]="ip -o link show"
  debug_code[7]="resolvectl status 2>/dev/null | sed -n '1,120p' || cat /etc/resolv.conf"

  local -a debug_desc=()
  debug_desc[1]="检查 9090/9091/7890/7891 端口监听:"
  debug_desc[2]="查看指定 PID 的进程信息:"
  debug_desc[3]="测试一下网络(仅proxy)"
  debug_desc[4]="测试一下网络(直连+proxy)"
  debug_desc[5]="测试需要的软件是否安装:"
  debug_desc[6]="看看是否有虚拟网卡 mihomo:"
  debug_desc[7]="dns debug"

  echo "== debug list =="
  local i
  for i in "${!debug_code[@]}"; do
    printf "  [%s] %s\n      %s\n" "$i" "${debug_desc[$i]:-}" "${debug_code[$i]}"
  done
  local idx
  read -r -p "select index: " idx

  for i in "${!debug_code[@]}"; do
    if [[ "$i" == "$idx" ]]; then
      echo "== run [$i] =="
      eval "${debug_code[$i]}"
      return $?
    fi
  done

  echo "invalid index: $idx"
  return 1
}

sysproxy_cmd() {
  local action="${1:-status}"
  command -v gsettings >/dev/null 2>&1 || die "gsettings not found; this sysproxy command is for GNOME/Ubuntu desktops"

  case "$action" in
    on)
      gsettings set org.gnome.system.proxy mode 'manual'
      gsettings set org.gnome.system.proxy.http host '127.0.0.1'
      gsettings set org.gnome.system.proxy.http port "${MIXED_PORT}"
      gsettings set org.gnome.system.proxy.https host '127.0.0.1'
      gsettings set org.gnome.system.proxy.https port "${MIXED_PORT}"
      gsettings set org.gnome.system.proxy.socks host '127.0.0.1'
      gsettings set org.gnome.system.proxy.socks port "${SOCK_PORT}"
      gsettings set org.gnome.system.proxy ignore-hosts "['localhost','127.0.0.0/8','::1','10.0.0.0/8','172.16.0.0/12','192.168.0.0/16']"
      info "system proxy enabled: http=127.0.0.1:${MIXED_PORT}, socks=127.0.0.1:${SOCK_PORT}"
      ;;
    off)
      gsettings set org.gnome.system.proxy mode 'none'
      info "system proxy disabled"
      ;;
    status)
      echo "mode=$(gsettings get org.gnome.system.proxy mode)"
      echo "http=$(gsettings get org.gnome.system.proxy.http host):$(gsettings get org.gnome.system.proxy.http port)"
      echo "https=$(gsettings get org.gnome.system.proxy.https host):$(gsettings get org.gnome.system.proxy.https port)"
      echo "socks=$(gsettings get org.gnome.system.proxy.socks host):$(gsettings get org.gnome.system.proxy.socks port)"
      ;;
    *)
      die "usage: $0 sysproxy [on|off|status]"
      ;;
  esac
}

doctor() {
  local prof cfg
  prof="$(current_profile 2>/dev/null || true)"
  cfg=""
  [[ -n "$prof" ]] && cfg="${MIHOMO_ROOT}/${prof}/config.yaml"

  echo "== mh doctor =="
  echo "root: ${MIHOMO_ROOT}"
  echo "bin:  ${MIHOMO_BIN}"
  echo "pid:  ${PID_FILE}"
  [[ -n "$prof" ]] && echo "profile: ${prof}" || echo "profile: <none>"
  [[ -n "$cfg" ]] && echo "config: ${cfg}"
  echo

  echo "== capability =="
  if command -v getcap >/dev/null 2>&1; then
    getcap "$MIHOMO_BIN" 2>/dev/null || true
  else
    echo "getcap not found"
  fi
  echo

  echo "== ports =="
  ss -lnt 2>/dev/null | grep -E ":(9090|7890|7891|1053)\\b" || echo "no mh ports listening"
  echo

  echo "== runtime =="
  tun_status
  show_runtime_routing_status
  proxy_env_status
  echo

  echo "== api =="
  api_get "/configs" 2>/dev/null | jq -r '{mode, "mixed-port": ."mixed-port", "socks-port": ."socks-port", "external-controller": ."external-controller"}' 2>/dev/null || echo "api unavailable"
  echo

  echo "== route =="
  ip route show default 2>/dev/null || true
  echo

  echo "== dns =="
  resolvectl status 2>/dev/null | sed -n '1,120p' || cat /etc/resolv.conf
  echo

  echo "== log tail =="
  if [[ -n "$prof" && -f "${MIHOMO_ROOT}/${prof}/mihomo.log" ]]; then
    tail -n 40 "${MIHOMO_ROOT}/${prof}/mihomo.log"
  else
    echo "no log file"
  fi
}

#=========================debug,上===============================

main() {
    if [[ -z "${1:-}"  ]]; then
        tun_status
        show_runtime_routing_status
        proxy_env_status
        speedtest_now 1000 || true
        # print_mihomo_status
        exit 0
    fi
  case "${1:-}" in
    add)
        cfg_add "$@";;
    start)
        [[ -n "${2:-}" ]] || die "usage: $0 start <name>"
        select_one "select" "${2}";;
    end)
        start_end "$@";;
    select)
        select_one "$@";;
    db)
        debug_one "$@";;
    doctor)
        doctor ;;
    env)
        show_proxy_exports ;;
    theme)
        shift; theme_cmd "${1:-show}" ;;
    sysproxy)
        shift; sysproxy_cmd "${1:-status}" ;;
    update)
        shift; update_providers_cmd "${1:-}" ;;
    mode)
        shift; mode_set "${1:-}" ;;
    rules)
        shift; rules_cmd "${1:-show}" ;;
    help|-h|--help) help ;;
    -l|--list|list) shift; list_nodes "${1:-}" ;;
    tun) shift; tun "${1:-toggle}" ;;
    *) pick_switch_with_delay "${1:-}" 1200 ;;
  esac
}
main "$@"
