#!/bin/bash

INSTALLER_CDN="https://tools.88de.de"
AUTO_CDN="https://auto.toolscdn.88de.de"
CDN_LIST="$AUTO_CDN"


cdn_name() {
    case "$1" in
        "$INSTALLER_CDN") printf '主站 tools.88de.de' ;;
        "$AUTO_CDN") printf '智能 CDN' ;;
        *)              printf '%s' "$1" ;;
    esac
}

cdn_url_order() {
    local preferred="$1" url
    printf '%s\n' "$preferred"
    if printf 'a\nb\n' | sort -R >/dev/null 2>&1; then
        for url in $CDN_LIST; do
            [ "$url" = "$preferred" ] && continue
            printf '%s\n' "$url"
        done | sort -R
    else
        for url in $CDN_LIST; do
            [ "$url" = "$preferred" ] && continue
            printf '%s\n' "$url"
        done
    fi
}

if [ "$(id -u)" -ne 0 ]; then
    printf '%s\n' "请使用 root 用户执行安装"
    exit 1
fi

SKIP_CACHE=""
UPDATE_MODE=""
TOOL_DIR="/opt/J-tools"
FORCED_CDN_URL=""
while [ $# -gt 0 ]; do
    case "$1" in
        -r)
            SKIP_CACHE=1
            ;;
        --update)
            UPDATE_MODE=1
            ;;
        --tool-dir)
            shift
            [ -n "${1:-}" ] && TOOL_DIR="$1"
            ;;
        --cdn-url)
            shift
            [ -n "${1:-}" ] && FORCED_CDN_URL="$1"
            ;;
    esac
    shift
done

installer_ensure_safe_cwd() {
    pwd >/dev/null 2>&1 && return 0
    cd / 2>/dev/null && return 0
    [ -n "$TOOL_DIR" ] && cd "$TOOL_DIR" 2>/dev/null && return 0
    return 1
}
installer_ensure_safe_cwd

mkdirdi_status() {
    local label="$1" status="$2" color="$3"
    printf '  %-22s %b%s%b\n' "$label" "$color" "$status" "$RESET"
}
RESET=$'\033[0m'
GREEN=$'\033[0;32m'
RED=$'\033[0;31m'
YELLOW=$'\033[0;33m'
GRAY=$'\033[38;5;244m'
START_TS=$(date +%s 2>/dev/null || echo "")

WORKDIR=$(mktemp -d /tmp/jt_install.XXXXXX 2>/dev/null || true)
if [ -z "$WORKDIR" ]; then
    WORKDIR="/tmp/jt_install.$$"
    [ -e "$WORKDIR" ] && {
        printf '%s\n' "临时目录已存在，安装中止"
        exit 1
    }
fi
mkdir -p "$WORKDIR/ext/bundle/core" "$WORKDIR/ext/bundle/modules" "$WORKDIR/ext/bundle/self" || exit 1
trap 'rm -rf "$WORKDIR" 2>/dev/null || true' EXIT

fmt_elapsed() {
    local total="$1" h m s
    case "$total" in
        ''|*[!0-9]*) return 1 ;;
    esac
    [ "$total" -lt 0 ] && return 1
    if [ "$total" -lt 60 ]; then
        printf '%ss' "$total"
        return 0
    fi
    if [ "$total" -lt 3600 ]; then
        m=$((total / 60))
        s=$((total % 60))
        printf '%sm %ss' "$m" "$s"
        return 0
    fi
    h=$((total / 3600))
    m=$(((total % 3600) / 60))
    s=$((total % 60))
    printf '%sh %sm %ss' "$h" "$m" "$s"
}

finish_elapsed_text() {
    local end_ts elapsed text
    end_ts=$(date +%s 2>/dev/null || echo "")
    case "$START_TS$end_ts" in
        *[!0-9]*|'') return 1 ;;
    esac
    elapsed=$((end_ts - START_TS))
    [ "$elapsed" -lt 0 ] && return 1
    text=$(fmt_elapsed "$elapsed" 2>/dev/null || true)
    [ -n "$text" ] || return 1
    printf '%s' "$text"
}

# ---- 首装使用智能资源 CDN；源失败时保留统一失败诊断 ----
SELECTED_CDN="$AUTO_CDN"
LOCKED_CDN=""
cdn_label=""
if [ -n "$SKIP_CACHE" ]; then
    # -r: 强制从主站 tools.88de.de 拉资源（可清缓存）
    SELECTED_CDN="$INSTALLER_CDN"
    cdn_label="强制主站 tools.88de.de"
elif [ -n "$FORCED_CDN_URL" ]; then
    SELECTED_CDN="$FORCED_CDN_URL"
    cdn_label="$(cdn_name "$SELECTED_CDN")"
else
    cdn_label="$(cdn_name "$SELECTED_CDN")"
fi

printf '%s\n' "=========================================="
if [ -n "$UPDATE_MODE" ]; then
    printf '    极 Tools 更新程序'
else
    printf '    极 Tools 安装程序'
fi
[ -n "$cdn_label" ] && printf '  %s' "$cdn_label"
printf '\n%s\n' "=========================================="

# Description: 使用统一资源 CDN 下载并保留失败诊断
# $1: 远程路径
# $2: 本地输出路径
# $3: 超时秒数（默认 10）
# Returns: 0 = 成功, 1 = 全部失败
append_multiline_var() {
    local var_name="$1" line="$2" current=""
    current="${!var_name}"
    if [ -n "$current" ]; then
        printf -v "$var_name" '%s\n%s' "$current" "$line"
    else
        printf -v "$var_name" '%s' "$line"
    fi
}

write_dl_status() {
    local file="$1" result="$2" curl_rc="$3" http_code="$4" reason="$5"
    [ -n "$file" ] || return 0
    {
        printf 'result=%s\n' "$result"
        printf 'curl_rc=%s\n' "$curl_rc"
        printf 'http_code=%s\n' "$http_code"
        printf 'reason=%s\n' "$reason"
    } > "$file" 2>/dev/null || true
}

read_dl_status_field() {
    local file="$1" key="$2" value=""
    [ -f "$file" ] || return 1
    value=$(sed -n "s/^$key=//p" "$file" 2>/dev/null | head -n 1)
    printf '%s' "$value"
}

classify_curl_failure() {
    local exit_code="$1" http_code="$2"
    case "$exit_code" in
        3|5) printf 'URL/代理配置异常（curl %s）' "$exit_code" ;;
        6) printf 'DNS/解析失败（curl 6）' ;;
        7|52|55|56) printf '连接失败（curl %s）' "$exit_code" ;;
        18) printf '下载内容不完整（curl 18）' ;;
        23|26) printf '本地写入失败（curl %s）' "$exit_code" ;;
        28) printf '连接超时（curl 28）' ;;
        35|51|58|60) printf 'TLS/证书失败（curl %s）' "$exit_code" ;;
        47) printf '重定向失败（curl 47）' ;;
        22)
            case "$http_code" in
                404) printf 'HTTP 404' ;;
                5[0-9][0-9]) printf 'HTTP 5xx' ;;
                [1-9][0-9][0-9]) printf 'HTTP %s' "$http_code" ;;
                *) printf 'HTTP 错误（curl 22）' ;;
            esac
            ;;
        *)
            case "$http_code" in
                404) printf 'HTTP 404' ;;
                5[0-9][0-9]) printf 'HTTP 5xx' ;;
                [1-9][0-9][0-9]) printf 'HTTP %s' "$http_code" ;;
                *) printf 'curl %s' "$exit_code" ;;
            esac
            ;;
    esac
}

download_failure_reason_from_state() {
    local state_file="$1" curl_rc http_code reason
    curl_rc=$(read_dl_status_field "$state_file" "curl_rc" 2>/dev/null || true)
    http_code=$(read_dl_status_field "$state_file" "http_code" 2>/dev/null || true)
    reason=$(read_dl_status_field "$state_file" "reason" 2>/dev/null || true)
    if [ -n "$reason" ]; then
        printf '%s' "$reason"
    elif [ -n "$curl_rc" ]; then
        classify_curl_failure "$curl_rc" "$http_code"
    else
        printf '%s' "状态采集失败"
    fi
}

download_failure_category_from_state() {
    local state_file="$1" curl_rc reason
    curl_rc=$(read_dl_status_field "$state_file" "curl_rc" 2>/dev/null || true)
    reason=$(read_dl_status_field "$state_file" "reason" 2>/dev/null || true)
    case "$curl_rc" in
        local_mkdir|23|26) printf '%s' "local_write" ;;
        *)
            case "$reason" in
                "临时目录创建失败") printf '%s' "local_write" ;;
                *) printf '%s' "remote_fetch" ;;
            esac
            ;;
    esac
}

dl_capture_success_from_tmpd() {
    local tmpd="$1" output="$2" status_file="$3"
    local done_file idx state_file curl_rc http_code reason
    for done_file in "$tmpd"/*.done; do
        [ -e "$done_file" ] || continue
        idx=${done_file##*/}
        idx=${idx%.done}
        state_file="$tmpd/$idx.status"
        curl_rc=$(read_dl_status_field "$state_file" "curl_rc" 2>/dev/null || true)
        http_code=$(read_dl_status_field "$state_file" "http_code" 2>/dev/null || true)
        reason=$(read_dl_status_field "$state_file" "reason" 2>/dev/null || true)
        mv "$done_file" "$output"
        write_dl_status "$status_file" "ok" "${curl_rc:-0}" "$http_code" "$reason"
        return 0
    done
    return 1
}

dl_capture_failure_from_tmpd() {
    local tmpd="$1" status_file="$2"
    local state_file result curl_rc http_code reason
    for state_file in "$tmpd"/*.status; do
        [ -e "$state_file" ] || continue
        result=$(read_dl_status_field "$state_file" "result" 2>/dev/null || true)
        if [ "$result" = "ok" ]; then
            curl_rc=$(read_dl_status_field "$state_file" "curl_rc" 2>/dev/null || true)
            http_code=$(read_dl_status_field "$state_file" "http_code" 2>/dev/null || true)
            write_dl_status "$status_file" "fail" "${curl_rc:-state_conflict}" "$http_code" "状态收集异常"
            return 0
        fi
    done
    for state_file in "$tmpd"/*.status; do
        [ -e "$state_file" ] || continue
        curl_rc=$(read_dl_status_field "$state_file" "curl_rc" 2>/dev/null || true)
        http_code=$(read_dl_status_field "$state_file" "http_code" 2>/dev/null || true)
        reason=$(read_dl_status_field "$state_file" "reason" 2>/dev/null || true)
        write_dl_status "$status_file" "fail" "$curl_rc" "$http_code" "$reason"
        return 0
    done
    write_dl_status "$status_file" "fail" "state_missing" "" "状态采集失败"
}

print_fail_lines_from_block() {
    local block="$1" line label
    while IFS= read -r line; do
        [ -z "$line" ] && continue
        label="${line%%[*}"
        [ -n "$label" ] && printf '  %s FAIL\n' "$label"
    done <<< "$block"
}

dl_file() {
    local path="$1" output="$2" timeout="${3:-10}" quiet="${4:-0}" fixed_url="${5:-}" status_file="${6:-}" cache_suffix="" url
    local urls=() i pid pids=() tmpd
    [ -n "$SKIP_CACHE" ] && cache_suffix="?r=$RANDOM"
    tmpd=$(mktemp -d /tmp/dl.XXXXXX) 2>/dev/null || {
        write_dl_status "$status_file" "fail" "local_mkdir" "" "临时目录创建失败"
        return 1
    }

    if [ -n "$fixed_url" ]; then
        urls=("$fixed_url")
    else
        for url in $(cdn_url_order "$SELECTED_CDN"); do
            urls+=("$url")
        done
    fi

    for i in "${!urls[@]}"; do
        (
            local curl_rc http_code reason
            if curl -fsSL --connect-timeout "$timeout" --max-time "$((timeout * 8))" -w '%{http_code}' -o "$tmpd/$i.part" "${urls[$i]}/$path${cache_suffix}" > "$tmpd/$i.code" 2> "$tmpd/$i.err"; then
                http_code=""
                [ -s "$tmpd/$i.code" ] && IFS= read -r http_code < "$tmpd/$i.code"
                write_dl_status "$tmpd/$i.status" "ok" "0" "$http_code" ""
                mv "$tmpd/$i.part" "$tmpd/$i.done"
            else
                curl_rc=$?
                http_code=""
                [ -s "$tmpd/$i.code" ] && IFS= read -r http_code < "$tmpd/$i.code"
                reason=$(classify_curl_failure "$curl_rc" "$http_code")
                write_dl_status "$tmpd/$i.status" "fail" "$curl_rc" "$http_code" "$reason"
            fi
        ) &
        pids+=($!)
    done

    local remaining=("${pids[@]}")
    while [ ${#remaining[@]} -gt 0 ]; do
        if dl_capture_success_from_tmpd "$tmpd" "$output" "$status_file"; then
            for pid in "${remaining[@]}"; do
                kill "$pid" 2>/dev/null || true
            done
            rm -rf "$tmpd"
            [ "$quiet" = "1" ] || printf '  ✔ %s\n' "$path"
            return 0
        fi
        sleep 0.1
        local new_remaining=()
        for pid in "${remaining[@]}"; do
            kill -0 "$pid" 2>/dev/null && new_remaining+=("$pid")
        done
        remaining=("${new_remaining[@]}")
    done

    if dl_capture_success_from_tmpd "$tmpd" "$output" "$status_file"; then
        rm -rf "$tmpd"
        [ "$quiet" = "1" ] || printf '  ✔ %s\n' "$path"
        return 0
    fi
    dl_capture_failure_from_tmpd "$tmpd" "$status_file"
    rm -rf "$tmpd"
    [ "$quiet" = "1" ] || printf '  %s FAIL\n' "$path"
    return 1
}

release_source_candidates() {
    local url
    if [ -n "$SKIP_CACHE" ]; then
        printf '%s\n' "$INSTALLER_CDN"
        return 0
    fi
    if [ -n "$FORCED_CDN_URL" ]; then
        printf '%s\n' "$FORCED_CDN_URL"
        return 0
    fi
    for url in $(cdn_url_order "$SELECTED_CDN"); do
        printf '%s\n' "$url"
    done
}

# Description: 优先下载 gz 包解压到 WORKDIR/ext/，失败则回退单文件下载
gz_extract() {
    [ -n "$GZ_DONE" ] && return 1
    local gz_file="$WORKDIR/ext.tar.gz"

    dl_file "ext.tar.gz" "$gz_file" 15 || return 1
    if tar xzf "$gz_file" -C "$WORKDIR" 2>/dev/null && [ -d "$WORKDIR/ext" ]; then
        rm -f "$gz_file"
        GZ_DONE=1
        return 0
    fi

    rm -f "$gz_file"
    printf '  ext.tar.gz FAIL\n'
    return 1
}

prepare_core_payloads() {
    local source gz_file gz_status_file gz_reason gz_category
    local bundle_time ext_bundle_time
    CORE_PREP_FAILED=""
    CORE_BUNDLE_META_TIME=""
    CORE_EXT_BUNDLE_TIME=""
    CORE_PREP_HAS_REMOTE_FETCH=0
    CORE_PREP_HAS_LOCAL_WRITE=0
    CORE_PREP_HAS_INCONSISTENT=0

    command -v tar >/dev/null 2>&1 || {
        CORE_PREP_HAS_LOCAL_WRITE=1
        append_multiline_var CORE_PREP_FAILED "ext.tar.gz[解压失败]"
        return 1
    }

    for source in $(release_source_candidates); do
        rm -rf "$WORKDIR/ext" 2>/dev/null || true
        rm -f "$WORKDIR/j-tools" "$WORKDIR/ext.tar.gz" "$WORKDIR/bundle_meta" \
            "$WORKDIR/ext.tar.gz.status" 2>/dev/null || true

        gz_file="$WORKDIR/ext.tar.gz"
        gz_status_file="$WORKDIR/ext.tar.gz.status"
        CORE_PREP_FAILED=""
        CORE_BUNDLE_META_TIME=""
        CORE_EXT_BUNDLE_TIME=""
        CORE_PREP_HAS_REMOTE_FETCH=0
        CORE_PREP_HAS_LOCAL_WRITE=0
        CORE_PREP_HAS_INCONSISTENT=0

        if ! dl_file "ext.tar.gz" "$gz_file" 15 1 "$source" "$gz_status_file"; then
            gz_reason=$(download_failure_reason_from_state "$gz_status_file")
            gz_category=$(download_failure_category_from_state "$gz_status_file")
            [ "$gz_category" = "local_write" ] && CORE_PREP_HAS_LOCAL_WRITE=1 || CORE_PREP_HAS_REMOTE_FETCH=1
            append_multiline_var CORE_PREP_FAILED "ext.tar.gz[下载失败:$gz_reason]"
            continue
        fi

        LOCKED_CDN="$source"
        SELECTED_CDN="$source"

        tar xzf "$gz_file" -C "$WORKDIR" 2>/dev/null || {
            CORE_PREP_HAS_INCONSISTENT=1
            append_multiline_var CORE_PREP_FAILED "ext.tar.gz[解压失败]"
            continue
        }
        [ -d "$WORKDIR/ext" ] && [ -s "$WORKDIR/j-tools" ] && [ -s "$WORKDIR/bundle_meta" ] || {
            CORE_PREP_HAS_INCONSISTENT=1
            append_multiline_var CORE_PREP_FAILED "ext.tar.gz[目录缺失]"
            continue
        }
        validate_bash_file "$WORKDIR/j-tools" || {
            CORE_PREP_HAS_INCONSISTENT=1
            append_multiline_var CORE_PREP_FAILED "j-tools[语法校验失败]"
            continue
        }
        validate_bundle_meta "$WORKDIR/bundle_meta" || {
            CORE_PREP_HAS_INCONSISTENT=1
            append_multiline_var CORE_PREP_FAILED "bundle_meta[字段无效]"
            continue
        }
        validate_bundle_meta "$WORKDIR/ext/.bundle_meta" || {
            CORE_PREP_HAS_INCONSISTENT=1
            append_multiline_var CORE_PREP_FAILED "ext/.bundle_meta[字段无效]"
            continue
        }

        bundle_time=$(read_bundle_built_at "$WORKDIR/bundle_meta" 2>/dev/null || true)
        ext_bundle_time=$(read_bundle_built_at "$WORKDIR/ext/.bundle_meta" 2>/dev/null || true)
        if [ "$ext_bundle_time" != "$bundle_time" ]; then
            CORE_PREP_HAS_INCONSISTENT=1
            CORE_BUNDLE_META_TIME="$bundle_time"
            CORE_EXT_BUNDLE_TIME="$ext_bundle_time"
            append_multiline_var CORE_PREP_FAILED "bundle_meta[与 ext/.bundle_meta 不一致]"
            continue
        fi

        rm -f "$gz_file" "$WORKDIR/ext.tar.gz.status" 2>/dev/null || true
        GZ_DONE=1
        CORE_PREP_FAILED=""
        CORE_PREP_HAS_REMOTE_FETCH=0
        CORE_PREP_HAS_LOCAL_WRITE=0
        CORE_PREP_HAS_INCONSISTENT=0
        printf '  ✔ %s\n' "ext.tar.gz（用户全量包）"
        return 0
    done

    rm -f "$WORKDIR/ext.tar.gz" "$WORKDIR/bundle_meta" "$WORKDIR/j-tools" 2>/dev/null || true
    print_fail_lines_from_block "$CORE_PREP_FAILED"
    return 1
}

validate_bash_file() {
    [ -f "$1" ] && [ -s "$1" ] && bash -n "$1" 2>/dev/null
}

read_bundle_built_at() {
    local meta="$1" line
    [ -f "$meta" ] || return 1
    line=$(sed -n 's/^built_at_shanghai=//p' "$meta" 2>/dev/null | head -n 1)
    [ -n "$line" ] || return 1
    line=${line#\'}
    line=${line%\'}
    line=${line#\"}
    line=${line%\"}
    [ -n "$line" ] || return 1
    printf '%s' "$line"
}

validate_bundle_meta() {
    [ -s "$1" ] || return 1
    [ -n "$(read_bundle_built_at "$1" 2>/dev/null || true)" ]
}

ensure_bundle_meta_file() {
    local meta="$WORKDIR/ext/.bundle_meta"
    validate_bundle_meta "$meta" && return 0
    rm -f "$meta" 2>/dev/null || true
    [ -n "$LOCKED_CDN" ] || return 1
    dl_file "bundle_meta" "$meta" 10 1 "$LOCKED_CDN" || return 1
    validate_bundle_meta "$meta" || {
        rm -f "$meta" 2>/dev/null || true
        return 1
    }
}

download_menu_file() {
    local remote_path="$1"
    local local_key="$2"
    local output_path="$3"
    mkdir -p "$(dirname "$output_path")" 2>/dev/null || true
    dl_file "$remote_path" "$output_path" 10 0 "$LOCKED_CDN" || {
        menu_failed="$menu_failed $local_key[下载失败]"
        rm -f "$output_path" 2>/dev/null || true
        return 1
    }
    validate_bash_file "$output_path" || {
        menu_failed="$menu_failed $local_key[校验失败]"
        rm -f "$output_path" 2>/dev/null || true
        return 1
    }
}

write_source_conf() {
    cat > "$TOOL_DIR/source.conf" << EOF
TOOL_URL=$LOCKED_CDN
EOF
}

write_j_launcher() {
    cat > /usr/local/bin/j << EOF
#!/bin/bash
TOOL_DIR="$TOOL_DIR"
if [ ! -f "\$TOOL_DIR/j-tools" ]; then
    printf '%s\n' "工具箱文件丢失，请重新安装"
    exit 1
fi
pwd >/dev/null 2>&1 || cd / 2>/dev/null || cd "\$TOOL_DIR" 2>/dev/null || true
JTOOLS_DIR="\$TOOL_DIR" exec "\$TOOL_DIR/j-tools"
EOF
    chmod +x /usr/local/bin/j 2>/dev/null || true
    ln -sf /usr/local/bin/j /usr/local/bin/J 2>/dev/null || true
}

BACKUP_DIR="$WORKDIR/rollback"
BACKUP_FILES=()
BACKUP_TARGETS=()
NEW_TARGETS=()

backup_existing_target() {
    local target="$1" slot
    if [ -e "$target" ] || [ -L "$target" ]; then
        mkdir -p "$BACKUP_DIR" 2>/dev/null || return 1
        slot=${#BACKUP_TARGETS[@]}
        cp -P "$target" "$BACKUP_DIR/$slot" 2>/dev/null || return 1
        BACKUP_FILES+=("$BACKUP_DIR/$slot")
        BACKUP_TARGETS+=("$target")
    else
        NEW_TARGETS+=("$target")
    fi
}

prepare_update_backups() {
    local file target
    BACKUP_FILES=()
    BACKUP_TARGETS=()
    NEW_TARGETS=()

    backup_existing_target "$TOOL_DIR/j-tools" || return 1
    backup_existing_target "$TOOL_DIR/source.conf" || return 1
    backup_existing_target "/usr/local/bin/j" || return 1

    if [ -d "$WORKDIR/ext" ]; then
        while IFS= read -r -d '' file; do
            target="$TOOL_DIR/${file#$WORKDIR/}"
            backup_existing_target "$target" || return 1
        done < <(find "$WORKDIR/ext" -type f -print0 2>/dev/null)
    fi
}

is_managed_payload_target() {
    case "$1" in
        ext/.bundle_meta|ext/apps.sh|ext/cpu_guard.sh|ext/cscan|ext/jtcdn_test|ext/jtprobe|ext/proxies.sh|ext/scanner|ext/security.sh|ext/tests.sh|ext/web.sh|\
        ext/bundle/core/app_market.sh|ext/bundle/core/docker_app.sh|ext/bundle/core/ctl.sh|ext/bundle/core/ctl_json.sh|ext/bundle/core/ctl_read.sh|ext/bundle/core/ctl_read_app.sh|ext/bundle/core/ctl_read_core.sh|ext/bundle/core/ctl_read_system.sh|ext/bundle/core/ctl_read_network.sh|ext/bundle/core/ctl_read_web.sh|ext/bundle/core/ctl_write.sh|\
        ext/bundle/modules/alist_tvbox.sh|ext/bundle/modules/certd.sh|ext/bundle/modules/compose_apps.sh|ext/bundle/modules/ddns_go.sh|ext/bundle/modules/dnsmgr.sh|ext/bundle/modules/download_clients.sh|ext/bundle/modules/qbittorrent.sh|ext/bundle/modules/transmission.sh|\
        ext/bundle/modules/emby.sh|ext/bundle/modules/iyuu.sh|ext/bundle/modules/komari.sh|ext/bundle/modules/media_automation_apps.sh|ext/bundle/modules/media_combo.sh|ext/bundle/modules/moviepilot.sh|\
        ext/bundle/modules/openlist.sh|ext/bundle/modules/single_container_apps.sh|ext/bundle/modules/qinglong.sh|ext/bundle/modules/uptime_kuma.sh|ext/bundle/modules/vertex.sh|ext/bundle/modules/octopus.sh|ext/bundle/modules/sub2api.sh|ext/bundle/modules/sub_store.sh|\
        ext/bundle/modules/emby/config.yml|ext/bundle/modules/emby/docker-compose.yml|\
        ext/webui/server.py|ext/webui/static/index.html|ext/webui/static/app.css|ext/webui/static/app.js|ext/webui/jtools-webui.service|ext/webui/API.md|\
        ext/switch-exit/src/auto-revert.sh|ext/switch-exit/src/boot-guard.sh|ext/switch-exit/src/config.py|ext/switch-exit/src/fetch_subscription.py|\
        ext/switch-exit/src/install.sh|ext/switch-exit/src/lib.sh|ext/switch-exit/src/parse_links.py|ext/switch-exit/src/payload-current.sh|\
        ext/switch-exit/src/platform.sh|ext/switch-exit/src/route.sh|ext/switch-exit/src/switch-exit|ext/switch-exit/src/uninstall.sh|\
        ext/switch-exit/src/xray_config.py|ext/switch-exit/src/xray-start.sh|ext/switch-exit/src/xray-stop.sh)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}
collect_stale_targets() {
    local file rel
    STALE_TARGETS=()
    [ -d "$TOOL_DIR/ext" ] || return 0
    while IFS= read -r -d "" file; do
        case "$file" in
            "$TOOL_DIR/ext/bundle/self/"*|"$TOOL_DIR/ext/switch-exit/AGENTS.md"|"$TOOL_DIR/ext/switch-exit/README.md"|"$TOOL_DIR/ext/switch-exit/测试清单.md"|"$TOOL_DIR/ext/switch-exit/设计说明.md"|*/__pycache__/*|*.pyc|*.pyo) continue ;;
        esac
        rel="${file#$TOOL_DIR/}"
        is_managed_payload_target "$rel" || continue
        [ -e "$WORKDIR/$rel" ] || { backup_existing_target "$file" || return 1; STALE_TARGETS+=("$file"); }
    done < <(find "$TOOL_DIR/ext" -type f -print0 2>/dev/null)
}

rollback_update_write() {
    local i target
    for i in "${!BACKUP_TARGETS[@]}"; do
        target="${BACKUP_TARGETS[$i]}"
        mkdir -p "$(dirname "$target")" 2>/dev/null || true
        cp -P "${BACKUP_FILES[$i]}" "$target" 2>/dev/null || true
    done
    for target in "${NEW_TARGETS[@]}"; do
        rm -f "$target" 2>/dev/null || true
    done
    [ -f /usr/local/bin/j ] && ln -sf /usr/local/bin/j /usr/local/bin/J 2>/dev/null || true
}


# B45-B: WebUI token + unit install (never enable/start)
installer_webui_ensure_token() {
    local dir="/etc/j-tools" token_file="/etc/j-tools/webui.token" tmp="" token=""
    mkdir -p "$dir" 2>/dev/null || return 1
    chmod 700 "$dir" 2>/dev/null || true
    if [ -e "$token_file" ] || [ -L "$token_file" ]; then
        if [ -L "$token_file" ] || [ ! -f "$token_file" ]; then
            printf '%b\n' "${YELLOW}WebUI token 路径不是普通文件（可能是 symlink），已跳过写入${RESET}"
            return 1
        fi
        chmod 600 "$token_file" 2>/dev/null || true
        token=$(tr -d '\r\n\t ' <"$token_file" 2>/dev/null || true)
        if printf '%s' "$token" | grep -Eq '^[0-9a-f]{64}$'; then
            return 0
        fi
        printf '%b\n' "${YELLOW}WebUI token 已存在但格式无效，已保留原文件${RESET}"
        return 1
    fi
    token=$(tr -dc 'a-f0-9' </dev/urandom 2>/dev/null | head -c 64)
    if [ "${#token}" -ne 64 ] || ! printf '%s' "$token" | grep -Eq '^[0-9a-f]{64}$'; then
        if command -v python3 >/dev/null 2>&1; then
            token=$(python3 -c 'import secrets; print(secrets.token_hex(32))' 2>/dev/null || true)
        fi
    fi
    if ! printf '%s' "$token" | grep -Eq '^[0-9a-f]{64}$'; then
        printf '%b\n' "${YELLOW}WebUI token 生成失败，已跳过${RESET}"
        return 1
    fi
    tmp=$(mktemp "$dir/.webui.token.XXXXXX" 2>/dev/null || true)
    [ -n "$tmp" ] || return 1
    printf '%s\n' "$token" >"$tmp" || { rm -f "$tmp"; return 1; }
    chmod 600 "$tmp" 2>/dev/null || { rm -f "$tmp"; return 1; }
    mv -f "$tmp" "$token_file" || { rm -f "$tmp"; return 1; }
    chmod 600 "$token_file" 2>/dev/null || true
    return 0
}

installer_webui_install_unit() {
    local src="$TOOL_DIR/ext/webui/jtools-webui.service"
    local dst="/etc/systemd/system/jtools-webui.service"
    [ -f "$src" ] || return 1
    if [ ! -d /etc/systemd/system ]; then
        return 1
    fi
    cp "$src" "$dst" 2>/dev/null || return 1
    chmod 644 "$dst" 2>/dev/null || true
    if command -v systemctl >/dev/null 2>&1; then
        # only reload unit catalog; never enable/start WebUI on source or install path
        systemctl daemon-reload 2>/dev/null || true
    fi
    return 0
}

installer_webui_setup() {
    # best-effort; toolbox install/update must still succeed without python3
    if [ ! -f "$TOOL_DIR/ext/webui/server.py" ]; then
        return 0
    fi
    if ! command -v python3 >/dev/null 2>&1; then
        printf '%b\n' "${YELLOW}未检测到 python3，WebUI 载荷已安装但不可用（未装依赖）${RESET}"
        return 0
    fi
    installer_webui_ensure_token || true
    installer_webui_install_unit || true
    return 0
}

cleanup_update_backups() {
    local file
    for file in "${BACKUP_FILES[@]}"; do
        rm -f "$file" 2>/dev/null || true
    done
}

core_failed=""
menu_failed=""
GZ_DONE=""
core_has_remote_fetch=0
core_has_local_write=0
core_has_release_inconsistency=0

if [ -n "$UPDATE_MODE" ]; then
    printf '正在更新工具箱...\n'
else
    printf '正在安装工具箱...\n'
fi

# ---- 核心载荷并行准备 ----
if ! prepare_core_payloads; then
    [ -n "$CORE_PREP_FAILED" ] && append_multiline_var core_failed "$CORE_PREP_FAILED" || append_multiline_var core_failed "ext.tar.gz[准备失败]"
    core_has_remote_fetch=$CORE_PREP_HAS_REMOTE_FETCH
    core_has_local_write=$CORE_PREP_HAS_LOCAL_WRITE
    core_has_release_inconsistency=$CORE_PREP_HAS_INCONSISTENT
fi

if [ -n "$GZ_DONE" ] && ! validate_bash_file "$WORKDIR/j-tools"; then
    append_multiline_var core_failed "j-tools[语法校验失败]"
    core_has_release_inconsistency=1
fi

if [ -n "$GZ_DONE" ]; then
    if ! validate_bash_file "$WORKDIR/ext/web.sh"; then
        rm -f "$WORKDIR/ext/web.sh" 2>/dev/null || true
        dl_file "ext/web.sh" "$WORKDIR/ext/web.sh" 10 0 "$LOCKED_CDN"
        if ! validate_bash_file "$WORKDIR/ext/web.sh"; then
            append_multiline_var core_failed "ext/web.sh[校验失败]"
            core_has_release_inconsistency=1
        fi
    fi

    for mod in apps.sh tests.sh proxies.sh security.sh; do
        if ! validate_bash_file "$WORKDIR/ext/$mod"; then
            rm -f "$WORKDIR/ext/$mod" 2>/dev/null || true
            download_menu_file "ext/$mod" "$mod" "$WORKDIR/ext/$mod"
        fi
    done
    for mod in app_market.sh docker_app.sh; do
        if ! validate_bash_file "$WORKDIR/ext/bundle/core/$mod"; then
            rm -f "$WORKDIR/ext/bundle/core/$mod" 2>/dev/null || true
            download_menu_file "ext/bundle/core/$mod" "$mod" "$WORKDIR/ext/bundle/core/$mod"
        fi
    done
    for mod in \
        alist_tvbox.sh certd.sh ddns_go.sh dnsmgr.sh qbittorrent.sh transmission.sh \
        emby.sh iyuu.sh komari.sh media_combo.sh moviepilot.sh \
        openlist.sh single_container_apps.sh sub2api.sh sub_store.sh; do
        if ! validate_bash_file "$WORKDIR/ext/bundle/modules/$mod"; then
            rm -f "$WORKDIR/ext/bundle/modules/$mod" 2>/dev/null || true
            download_menu_file "ext/bundle/modules/$mod" "$mod" "$WORKDIR/ext/bundle/modules/$mod"
        fi
    done

    for spec in \
        "cpu_guard.sh|ext/cpu_guard.sh|cpu_guard.sh" \
        "jtcdn_test|ext/jtcdn_test|jtcdn_test"; do
        IFS='|' read -r rel remote_path local_key <<< "$spec"
        if ! validate_bash_file "$WORKDIR/ext/$rel"; then
            rm -f "$WORKDIR/ext/$rel" 2>/dev/null || true
            download_menu_file "$remote_path" "$local_key" "$WORKDIR/ext/$rel"
        fi
    done
fi

ensure_bundle_meta_file >/dev/null 2>&1 || true

# ---- 核心失败 → 中止 ----
if [ -n "$core_failed" ]; then
    printf '\n'
    [ -n "$LOCKED_CDN" ] && mkdirdi_status "发布源" "$(cdn_name "$LOCKED_CDN")" "$YELLOW"
    [ -n "$LOCKED_CDN" ] && mkdirdi_status "发布地址" "$LOCKED_CDN" "$YELLOW"
    while IFS= read -r f; do
        [ -z "$f" ] && continue
        case "$f" in
            *"[下载失败:"*"]")
                label="${f%%[*}"
                detail="${f#*[下载失败:}"
                detail="${detail%]}"
                mkdirdi_status "$label" "下载失败（$detail）" "$RED"
                ;;
            *"[语法校验失败]")
                mkdirdi_status "${f%\[*}" "语法校验失败" "$RED"
                ;;
            *"[解压失败]")
                mkdirdi_status "${f%\[*}" "解压失败" "$RED"
                ;;
            *"[目录缺失]")
                mkdirdi_status "${f%\[*}" "目录缺失" "$RED"
                ;;
            *"[字段无效]")
                mkdirdi_status "${f%\[*}" "字段无效" "$RED"
                ;;
            *"[与 ext/.bundle_meta 不一致]")
                mkdirdi_status "${f%\[*}" "与 ext/.bundle_meta 不一致" "$RED"
                ;;
            *"[写入 ext/.bundle_meta 失败]")
                mkdirdi_status "${f%\[*}" "写入 ext/.bundle_meta 失败" "$RED"
                ;;
            *)
                mkdirdi_status "$f" "校验失败" "$RED"
                ;;
        esac
    done <<< "$core_failed"
    if [ -n "$CORE_BUNDLE_META_TIME" ] || [ -n "$CORE_EXT_BUNDLE_TIME" ]; then
        [ -n "$CORE_BUNDLE_META_TIME" ] && mkdirdi_status "顶层 bundle_meta" "$CORE_BUNDLE_META_TIME" "$YELLOW"
        [ -n "$CORE_EXT_BUNDLE_TIME" ] && mkdirdi_status "包内 ext/.bundle_meta" "$CORE_EXT_BUNDLE_TIME" "$YELLOW"
    fi
    if [ "$core_has_local_write" -eq 1 ]; then
        mkdirdi_status "排查建议" "检查 /tmp 可写性与磁盘空间" "$YELLOW"
    fi
    if [ -n "$UPDATE_MODE" ]; then
        if [ "$core_has_release_inconsistency" -eq 1 ]; then
            printf '\n%b\n' "${RED}核心发布物彼此不一致或已损坏，更新中止${RESET}"
        elif [ "$core_has_local_write" -eq 1 ]; then
            printf '\n%b\n' "${RED}本地临时写入环境异常，更新中止${RESET}"
        else
            printf '\n%b\n' "${RED}核心发布集文件获取失败，更新中止${RESET}"
        fi
    else
        if [ "$core_has_release_inconsistency" -eq 1 ]; then
            if [ -n "$SKIP_CACHE" ]; then
                printf '\n%b\n' "${RED}主站 tools.88de.de 发布物彼此不一致或已损坏，安装中止${RESET}"
            else
                printf '\n%b\n' "${RED}核心发布物彼此不一致或已损坏，安装中止${RESET}"
            fi
        elif [ "$core_has_local_write" -eq 1 ]; then
            printf '\n%b\n' "${RED}本地临时写入环境异常，安装中止${RESET}"
        else
            if [ -n "$SKIP_CACHE" ]; then
                printf '\n%b\n' "${RED}主站 tools.88de.de 文件获取失败，安装中止${RESET}"
            else
                printf '\n%b\n' "${RED}核心发布集文件获取失败，安装中止${RESET}"
            fi
        fi
        printf '%b\n' "${GRAY}请重新执行: bash <(curl -sL tools.88de.de)${RESET}"
    fi
    exit 1
fi

# ---- 写入 ----
mkdir -p "$TOOL_DIR/ext/bundle/core" "$TOOL_DIR/ext/bundle/modules" "$TOOL_DIR/ext/bundle/self" || exit 1

if [ -n "$UPDATE_MODE" ]; then
    # B42-F1: backups first; only on success collect managed stale (before copy).
    # Stale deletion remains after successful j-tools/ext copy below.
    prepare_update_backups || {
        printf '\n%b\n' "${RED}更新备份失败，已取消${RESET}"
        exit 1
    }
    collect_stale_targets || {
        printf '\n%b\n' "${RED}旧载荷清理清单准备失败，已取消${RESET}"
        exit 1
    }
fi

write_failed=0
if [ -f "$WORKDIR/j-tools" ]; then
    cp "$WORKDIR/j-tools" "$TOOL_DIR/j-tools" 2>/dev/null || write_failed=1
    [ "$write_failed" -eq 0 ] && chmod 755 "$TOOL_DIR/j-tools" 2>/dev/null || true
else
    write_failed=1
fi

if [ "$write_failed" -eq 0 ] && [ -d "$WORKDIR/ext" ]; then
    cp -R "$WORKDIR/ext/." "$TOOL_DIR/ext/" 2>/dev/null || write_failed=1
    if [ "$write_failed" -eq 0 ] && [ -n "$UPDATE_MODE" ]; then
        for target in "${STALE_TARGETS[@]}"; do
            rm -f "$target" 2>/dev/null || write_failed=1
        done
    fi
fi

[ "$write_failed" -eq 0 ] && write_source_conf || write_failed=1
[ "$write_failed" -eq 0 ] && write_j_launcher || write_failed=1

if [ "$write_failed" -eq 1 ]; then
    if [ -n "$UPDATE_MODE" ]; then
        rollback_update_write
        printf '\n%b\n' "${RED}更新写入失败，已回退${RESET}"
    fi
    exit 1
fi

for f in "$TOOL_DIR/j-tools" "$TOOL_DIR/ext"/*.sh "$TOOL_DIR/ext/jtcdn_test" "$TOOL_DIR/ext/bundle/core"/*.sh "$TOOL_DIR/ext/bundle/modules"/*.sh "$TOOL_DIR/ext/bundle/self"/*.sh; do
    [ -f "$f" ] && chmod +x "$f" 2>/dev/null || true
done

rm -f "$TOOL_DIR/.version" 2>/dev/null || true
[ -n "$UPDATE_MODE" ] && cleanup_update_backups
installer_webui_setup

if [ -n "$menu_failed" ]; then
    if [ -n "$UPDATE_MODE" ]; then
        printf '\n%b\n' "${YELLOW}以下附加文件未更新，已跳过：${RESET}"
    else
        printf '\n%b\n' "${YELLOW}以下附加文件未安装，已跳过：${RESET}"
    fi
    for f in $menu_failed; do
        mkdirdi_status "$f" "已跳过" "$YELLOW"
    done
fi

if [ -n "$UPDATE_MODE" ]; then
    finish_text=$(finish_elapsed_text 2>/dev/null || true)
    if [ -n "$finish_text" ]; then
        printf '\n%s\n\n' "更新完成！（耗时：$finish_text）"
    else
        printf '\n%s\n\n' "更新完成！"
    fi
    exit 0
fi

finish_text=$(finish_elapsed_text 2>/dev/null || true)
if [ -n "$finish_text" ]; then
    printf '\n%s\n%s\n' "安装完成！（耗时：$finish_text）" "快捷启动命令：j  或  J"
else
    printf '\n%s\n%s\n' "安装完成！" "快捷启动命令：j  或  J"
fi
printf '\n'
installer_ensure_safe_cwd
JTOOLS_SKIP_CLEAR_ONCE=1 JTOOLS_DIR="$TOOL_DIR" exec "$TOOL_DIR/j-tools"
