#!/usr/bin/env bash
set -euo pipefail

readonly PROJECT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly MANAGED_STATE="${PROJECT_DIR}/runtime/mvp/state.json"
readonly INSTALL_MARKER="${PROJECT_DIR}/.clash-install.json"

# Legacy commands retain their existing environment file. Managed subscriptions
# keep their own state; stale legacy settings must not override their defaults.
case "${1:-menu}" in
    init|start|stop|restart|doctor|dashboard|install|update|version|mode|switch|groups|nodes|delay|test|logs)
        if [[ ! -e "$MANAGED_STATE" && ! -e "$INSTALL_MARKER" && -r "${PROJECT_DIR}/.env" ]]; then
            # shellcheck disable=SC1091
            source "${PROJECT_DIR}/.env"
        fi
        ;;
esac

readonly CONFIGURE_SCRIPT="${CLASH_CONFIGURE_SCRIPT:-${PROJECT_DIR}/scripts/configure_env.sh}"
readonly DOCTOR_SCRIPT="${CLASH_DOCTOR_SCRIPT:-${PROJECT_DIR}/scripts/doctor.sh}"
readonly DASHBOARD_SCRIPT="${CLASH_DASHBOARD_SCRIPT:-${PROJECT_DIR}/scripts/dashboard.sh}"
readonly INSTALL_SCRIPT="${CLASH_INSTALL_SCRIPT:-${PROJECT_DIR}/scripts/install_mihomo.sh}"
readonly CONNECTIVITY_SCRIPT="${CLASH_CONNECTIVITY_SCRIPT:-${PROJECT_DIR}/scripts/connectivity_check.sh}"
readonly SHELL_INTEGRATION_SCRIPT="${CLASH_SHELL_INTEGRATION_SCRIPT:-${PROJECT_DIR}/scripts/shell_integration.sh}"
readonly RUNTIME_SCRIPT="${CLASH_RUNTIME_SCRIPT:-${PROJECT_DIR}/scripts/mihomo_runtime.sh}"
readonly START_SCRIPT="${CLASH_START_SCRIPT:-${PROJECT_DIR}/start.sh}"
readonly RESTART_SCRIPT="${CLASH_RESTART_SCRIPT:-${PROJECT_DIR}/restart.sh}"
readonly SHUTDOWN_SCRIPT="${CLASH_SHUTDOWN_SCRIPT:-${PROJECT_DIR}/shutdown.sh}"
readonly SELECTOR_SCRIPT="${CLASH_SELECTOR_SCRIPT:-${PROJECT_DIR}/scripts/clash_proxy-selector.sh}"
readonly UNINSTALL_SCRIPT="${CLASH_UNINSTALL_SCRIPT:-${PROJECT_DIR}/scripts/uninstall.py}"
readonly UPGRADE_SCRIPT="${CLASH_UPGRADE_SCRIPT:-${PROJECT_DIR}/scripts/upgrade.py}"
readonly MVP_SCRIPT="${CLASH_MVP_SCRIPT:-${PROJECT_DIR}/scripts/terminal_mvp.py}"
readonly CONFIG_FILE="${MIHOMO_CONFIG:-${PROJECT_DIR}/conf/config.yaml}"
readonly BINARY_FILE="${MIHOMO_BINARY:-${PROJECT_DIR}/bin/mihomo}"
readonly LOG_FILE="${MIHOMO_LOG:-${PROJECT_DIR}/logs/mihomo.log}"
readonly DASHBOARD_URL="${CLASH_DASHBOARD_URL:-${MIHOMO_API_URL:-http://127.0.0.1:9090}/ui}"

die() {
    printf '错误：%s\n' "$*" >&2
    exit 1
}

warn() {
    printf '警告：%s\n' "$*" >&2
}

require_executable() {
    [[ -x "$1" ]] || die "缺少可执行文件：$1"
}

print_proxy_env() {
    local http_proxy_url="${CLASH_HTTP_PROXY:-http://127.0.0.1:7890}"
    local socks_proxy_url="${CLASH_SOCKS_PROXY:-socks5h://127.0.0.1:7891}"
    local no_proxy_list="${CLASH_NO_PROXY:-127.0.0.1,localhost,::1}"

    printf 'export http_proxy=%q\n' "$http_proxy_url"
    printf 'export https_proxy=%q\n' "$http_proxy_url"
    printf 'export HTTP_PROXY=%q\n' "$http_proxy_url"
    printf 'export HTTPS_PROXY=%q\n' "$http_proxy_url"
    printf 'export all_proxy=%q\n' "$socks_proxy_url"
    printf 'export ALL_PROXY=%q\n' "$socks_proxy_url"
    printf 'export no_proxy=%q\n' "$no_proxy_list"
    printf 'export NO_PROXY=%q\n' "$no_proxy_list"
}

print_proxy_unset() {
    printf '%s\n' \
        'unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY' \
        'unset all_proxy ALL_PROXY no_proxy NO_PROXY' \
        'unset ftp_proxy FTP_PROXY socks_proxy SOCKS_PROXY rsync_proxy RSYNC_PROXY'
}

command_start() {
    if [[ -e "$MANAGED_STATE" || -e "$INSTALL_MARKER" ]]; then
        command_mvp start
    else
        require_executable "$START_SCRIPT"
        "$START_SCRIPT"
    fi
}

command_stop() {
    if [[ -e "$MANAGED_STATE" || -e "$INSTALL_MARKER" ]]; then
        command_mvp stop
    else
        require_executable "$SHUTDOWN_SCRIPT"
        "$SHUTDOWN_SCRIPT"
    fi
}

command_restart() {
    if [[ -e "$MANAGED_STATE" || -e "$INSTALL_MARKER" ]]; then
        command_mvp restart
    else
        require_executable "$RESTART_SCRIPT"
        "$RESTART_SCRIPT"
    fi
}

command_python() {
    local interpreter="${PROJECT_DIR}/python/bin/python3"
    if [[ ! -x "$interpreter" ]]; then
        [[ ! -e "$INSTALL_MARKER" ]] || die "项目独立 Python 缺失，请重新运行安装器修复此安装。"
        interpreter=$(command -v python3) || die "源码运行需要 Python 3；也可使用完整安装包。"
    fi
    "$interpreter" -E -s -B "$@"
}

command_mvp() {
    [[ -r "$MVP_SCRIPT" ]] || die "缺少订阅管理程序：$MVP_SCRIPT"
    command_python "$MVP_SCRIPT" "$@"
}

command_uninstall() {
    [[ $# -eq 0 || ( $# -eq 1 && "$1" == --purge ) ]] || die "用法：clash uninstall [--purge]"
    [[ -r "$UNINSTALL_SCRIPT" ]] || die "缺少卸载程序，请重新运行安装器修复此安装。"
    command_python "$UNINSTALL_SCRIPT" "$@"
}

command_upgrade() {
    [[ -r "$UPGRADE_SCRIPT" ]] || die "缺少升级程序，请使用最新版安装器的 --upgrade 选项。"
    command_python "$UPGRADE_SCRIPT" --root "$PROJECT_DIR" "$@"
}

command_status() {
    command_mvp status
}

command_init() {
    local mode value

    case "${1:-}" in
        --config)
            [[ $# -eq 2 ]] || die "用法：clash init --config FILE"
            mode="config"
            value="$2"
            ;;
        "")
            die "用法：clash init URL | clash init --config FILE"
            ;;
        -*)
            die "未知 init 选项：$1"
            ;;
        *)
            [[ $# -eq 1 ]] || die "用法：clash init URL"
            mode="subscription"
            value="$1"
            ;;
    esac

    require_executable "$CONFIGURE_SCRIPT"
    "$CONFIGURE_SCRIPT" "$mode" "$value"
    command_doctor preflight
    command_start
    if ! command_test; then
        warn "订阅已加载，但出站代理测试失败；请选择其他节点后运行 clash test"
    fi
    printf '\n初始化完成。常用命令：clash status / clash dashboard / clash switch\n'
    command_dashboard
}

command_doctor() {
    require_executable "$DOCTOR_SCRIPT"
    if [[ "${1:-}" == preflight ]]; then
        DOCTOR_ALLOW_MISSING_MIHOMO=1 "$DOCTOR_SCRIPT"
    else
        "$DOCTOR_SCRIPT"
    fi
}

command_shell() {
    command_mvp ensure >&2
    local shell_path="${CLASH_SHELL:-${SHELL:-/bin/bash}}"
    [[ -x "$shell_path" ]] || die "shell 不可执行：$shell_path"

    export http_proxy="${CLASH_HTTP_PROXY:-http://127.0.0.1:7890}"
    export https_proxy="$http_proxy"
    export HTTP_PROXY="$http_proxy"
    export HTTPS_PROXY="$http_proxy"
    export all_proxy="${CLASH_SOCKS_PROXY:-socks5h://127.0.0.1:7891}"
    export ALL_PROXY="$all_proxy"
    export no_proxy="${CLASH_NO_PROXY:-127.0.0.1,localhost,::1}"
    export NO_PROXY="$no_proxy"
    exec "$shell_path"
}

command_menu() {
    command_mvp menu "$@"
}

command_selector() {
    require_executable "$SELECTOR_SCRIPT"
    "$SELECTOR_SCRIPT" "$@"
}

command_test() {
    require_executable "$CONNECTIVITY_SCRIPT"
    "$CONNECTIVITY_SCRIPT" "$@"
}

command_shell_integration() {
    require_executable "$SHELL_INTEGRATION_SCRIPT"
    "$SHELL_INTEGRATION_SCRIPT" "$@"
}

command_logs() {
    local tail_args=(-n "${CLASH_LOG_LINES:-100}")
    [[ -r "$LOG_FILE" ]] || die "日志文件不可读：$LOG_FILE"
    case "${1:-}" in
        "") ;;
        -f|--follow) tail_args+=(-f) ;;
        *) die "usage: ./clash logs [--follow]" ;;
    esac
    tail "${tail_args[@]}" -- "$LOG_FILE"
}

command_dashboard() {
    require_executable "$DASHBOARD_SCRIPT"
    "$DASHBOARD_SCRIPT" "$@"
}

command_install() {
    require_executable "$INSTALL_SCRIPT"
    "$INSTALL_SCRIPT"
}

command_version() {
    require_executable "$RUNTIME_SCRIPT"
    "$RUNTIME_SCRIPT" version
}

usage() {
    cat <<'EOF'
用法：clash [命令]

  clash                 打开分栏工作台：节点、模式、订阅、诊断
  clash menu --plain     使用纯文本菜单
  clash start           启动内核，不改变当前终端代理
  clash stop            停止整个内核，影响所有使用它的会话
  clash restart         重启内核，不改变当前终端代理
  clash on              启动内核并开启当前终端代理
  clash off             关闭当前终端代理，保留内核运行
  clash status          查看当前终端和内核状态
  clash shell-init      安装当前用户的命令与 shell 集成
  clash uninstall       卸载程序，保留订阅与配置数据
  clash uninstall --purge  卸载并删除项目数据
  clash upgrade --check 检查程序新版本
  clash upgrade         升级整个程序，保留配置和订阅
  clash upgrade --version vX.Y.Z  安装指定版本
  clash upgrade --archive FILE --sha256 HASH  使用离线发行包升级
  clash rollback        恢复上一个程序版本
  clash recover         恢复中断的升级
  clash app-version     查看程序版本（version 仍查看内核版本）
  clash help            显示帮助

第一次使用：运行 ./clash install 安装内核，再运行 ./clash shell-init。
按提示加载 shell 集成后，运行 clash 添加订阅。
开关当前终端代理需要 shell 集成。未安装时也可执行：
  eval "$(./clash on)"
  eval "$(./clash off)"

日常使用：clash on → 使用 curl、Git 等命令 → clash off。
只启动内核：clash start；彻底停止：clash stop。
多个 SSH 终端各自开关；clash off 不会中断其他终端。
clash stop 会中断使用此内核的代理连接；其他会话需自行 clash off。
已安装集成时，clash stop 也会清理当前终端代理变量。
未启用多订阅管理时仍兼容旧版 init；启用后请通过菜单管理配置。
EOF
}

command="${1:-menu}"
[[ $# -eq 0 ]] || shift

# Once subscriptions are managed by the menu, legacy mutators must not replace
# the config or change API selections behind the manifest's back.
if [[ -e "$MANAGED_STATE" || -e "$INSTALL_MARKER" ]]; then
    case "$command" in
        init|mode|switch)
            die "已启用多订阅管理。请运行 clash 菜单修改配置、模式或节点；开启代理请用 clash on，重启内核请进入菜单的诊断与修复。"
            ;;
    esac
fi

# Hold a shared lock throughout normal CLI and TUI operations. Upgrade and
# uninstall acquire an exclusive lock in their Python entry points. The wrapper
# recognizes only a live inherited descriptor, never a boolean bypass flag.
case "$command" in
    help|-h|--help|env|off|shell|upgrade|rollback|recover|app-version|uninstall) ;;
    *)
        lock_helper="$PROJECT_DIR/scripts/operation_lock.py"
        if [[ -f "$lock_helper" ]]; then
            if ! command_python -c 'import sys; sys.path.insert(0, sys.argv[1]); from operation_lock import inherited_fd; sys.exit(inherited_fd(sys.argv[2]) is None)' "$PROJECT_DIR/scripts" "$PROJECT_DIR"; then
                command_python "$lock_helper" --root "$PROJECT_DIR" -- bash "$PROJECT_DIR/clash" "$command" "$@"
                exit $?
            fi
        fi
        ;;
esac

case "$command" in
    upgrade) command_upgrade upgrade "$@" ;;
    rollback|recover|app-version) [[ $# -eq 0 ]] || die "用法：clash $command"; command_upgrade "$command" ;;
    init) command_init "$@" ;;
    start) [[ $# -eq 0 ]] || die "用法：clash start"; command_start ;;
    stop) [[ $# -eq 0 ]] || die "用法：clash stop"; command_stop ;;
    restart) [[ $# -eq 0 ]] || die "用法：clash restart"; command_restart ;;
    status) [[ $# -eq 0 ]] || die "用法：clash status"; command_status ;;
    ensure) [[ $# -eq 0 ]] || die "用法：clash ensure"; command_mvp ensure ;;
    env) [[ $# -eq 0 ]] || die "用法：clash env"; print_proxy_env ;;
    on)
        [[ $# -eq 0 ]] || die "用法：clash on"
        command_mvp ensure >&2
        printf '%s\n' '内核已就绪。当前终端请执行：eval "$(./clash on)"；也可运行 ./clash shell-init 安装集成。' >&2
        print_proxy_env
        ;;
    off)
        [[ $# -eq 0 ]] || die "用法：clash off"
        printf '%s\n' '当前命令只打印清理语句；要让当前终端生效，请执行：eval "$(clash off)"' >&2
        print_proxy_unset
        ;;
    shell) [[ $# -eq 0 ]] || die "用法：clash shell"; command_shell ;;
    menu)
        [[ $# -eq 0 || ( $# -eq 1 && "$1" == --plain ) ]] || die "用法：clash menu [--plain]"
        command_menu "$@"
        ;;
    mode) [[ $# -le 1 ]] || die "用法：clash mode [MODE]"; command_selector mode "$@" ;;
    switch) [[ $# -le 2 ]] || die "用法：clash switch [QUERY] [GROUP]"; command_selector switch "$@" ;;
    groups) [[ $# -eq 0 ]] || die "用法：clash groups"; command_selector groups ;;
    nodes) [[ $# -le 1 ]] || die "用法：clash nodes [GROUP]"; command_selector nodes "$@" ;;
    delay) [[ $# -le 2 ]] || die "用法：clash delay [GROUP] [URL]"; command_selector delay "$@" ;;
    test) command_test "$@" ;;
    shell-init) command_shell_integration install "$@" ;;
    shell-uninstall) command_shell_integration uninstall "$@" ;;
    uninstall) command_uninstall "$@" ;;
    logs) command_logs "$@" ;;
    doctor) [[ $# -eq 0 ]] || die "用法：clash doctor"; command_doctor ;;
    dashboard) command_dashboard "$@" ;;
    install|update) [[ $# -eq 0 ]] || die "用法：clash $command"; command_install ;;
    version) [[ $# -eq 0 ]] || die "usage: ./clash version"; command_version ;;
    help|-h|--help) usage ;;
    *) die "未知命令：$command（运行 clash help 查看帮助）" ;;
esac
