#!/usr/bin/env bash
# compare_platforms_for_clientpatch — 比對 client patch 兩版本的平台差異
#
# 用法:
#   compare_platforms_for_clientpatch [--action <name>] [--a-label <str>] [--b-label <str>] [--thread-tag <tag>] [--no-notify] [--no-exclude] <A_old> <B_new>
#
# --action 可指定場景，會自動推導 a-label / b-label / thread-tag / title / exclude：
#   "test_update"  A_LABEL 優先讀 $COMPARE_PLATFORMS_STATE_DIR/test_update.last（由 TestCtrl 寫入），
#                  否則 fallback heuristic（掃 $HOME/patch/ 字典序倒數第二）；
#                  B_LABEL = $HOME/patch/ 字典序倒數第一；thread-tag=test_patch，套用 exclude
#   "live_sync"    A=$WORKING_DIRECTORY/www/${GAME_TYPE}-TEST_SYNCTOLIVE（複製到 /tmp/…_RESTORE 後依 setup.txt
#                  的 NFSExtension 批次改名），B=$WORKING_DIRECTORY/www/${GAME_TYPE}-TEST；
#                  label 用目錄名；thread-tag=client_sync；預設 --no-exclude（全比）；
#                  positional A、B 可省略，由 action 自動推導
#   (未設定)        維持 CLI 原行為，title="Client Patch"，套用 exclude
#
# --no-exclude  強制忽略 conf 的 COMPARE_PLATFORMS_EXCLUDE_PATHS（完整比對）。
#               顯式傳入會覆寫 action 的預設值。
#
# 差異三類（差異數量全部計入；差異大小只計 B 端與新版）：
#   A 獨有      — A 有 B 無；計入數量，**不計入**差異大小（舊版才有，新版不需下載）
#   B 獨有      — B 有 A 無；計入數量，計入差異大小 = sizeB
#   內容不同    — md5 不同；計入數量，計入差異大小 = sizeB（新版 size）
#
# 比對方法：md5 內容比對（完全遞迴；排除 EXCLUDE_PATHS 指定的子路徑）
#
# 通知：
#   預設：透過 GOOGLE_CHAT_WEBHOOK_URL 直接 curl（單獨訊息）
#   --thread-tag <tag>：透過 send_chatbot_text_only_by_thread_tag 發到同 thread
#   --no-notify：完全不發
#
# 輸出：
#   螢幕 stdout（呼叫者可 tee 接）+ www/download/ 下的 Log（URL 可存取）
#
# Exit code: 0 = 成功跑完（不論有無差異），非 0 = 錯誤

set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONF_FILE="$SCRIPT_DIR/compare_platforms_for_clientpatch.conf"

# 先 source .gamerc 取得 WORKING_DIRECTORY / GOOGLE_CHAT_WEBHOOK_URL / GAME_TYPE / COUNTRY_CODE_DISPLAY
# （conf 內可能用到 $WORKING_DIRECTORY，所以順序要在 conf 之前）
set +u
[[ -f "$HOME/.gamerc" ]] && source "$HOME/.gamerc"
set -u

[[ -f "$CONF_FILE" ]] || { echo "❌ 設定檔不存在: $CONF_FILE" >&2; exit 1; }
source "$CONF_FILE"

# 必要環境變數檢查；缺 .gamerc 時不要產生 ?-? 標題，明確 fallback 到 unknown 並警告
: "${WORKING_DIRECTORY:?需在 .gamerc 設定 WORKING_DIRECTORY（檢查 ~/.gamerc 是否存在/可讀）}"
if [[ -z "${GAME_TYPE:-}" ]]; then
    echo "⚠ GAME_TYPE 未設定 → 標題顯示 'unknown'（建議補進 .gamerc）" >&2
    GAME_TYPE="unknown"
fi
if [[ -z "${COUNTRY_CODE_DISPLAY:-}" ]]; then
    echo "⚠ COUNTRY_CODE_DISPLAY 未設定 → 標題顯示 'unknown'" >&2
    COUNTRY_CODE_DISPLAY="unknown"
fi

# ── arg parsing（旗標可放任何位置；positional 依順序收集）──
ACTION=""
THREAD_TAG=""
DO_NOTIFY=1
A_LABEL=""
B_LABEL=""
NO_EXCLUDE=0
NO_EXCLUDE_EXPLICIT=0
EMPTY_A=0
_POSITIONAL=()
while (( $# > 0 )); do
    case "$1" in
        --action)      ACTION="$2"; shift 2 ;;
        --a-label)     A_LABEL="$2"; shift 2 ;;
        --b-label)     B_LABEL="$2"; shift 2 ;;
        --thread-tag)  THREAD_TAG="$2"; shift 2 ;;
        --no-notify)   DO_NOTIFY=0; shift ;;
        --no-exclude)  NO_EXCLUDE=1; NO_EXCLUDE_EXPLICIT=1; shift ;;
        --help|-h)
            sed -n '2,/^# Exit code/p' "$0" | sed 's/^# \{0,1\}//'
            exit 0
            ;;
        --)            shift; _POSITIONAL+=("$@"); break ;;
        --*)           echo "未知選項: $1" >&2; echo "使用 --help 查看用法" >&2; exit 1 ;;
        *)             _POSITIONAL+=("$1"); shift ;;
    esac
done
set -- "${_POSITIONAL[@]:-}"

# ── 取 positional（action 沒 set 時需要由呼叫端傳）──
A="${1:-}"
B="${2:-}"

usage() {
    echo "用法: $0 [--action <name>] [--a-label <str>] [--b-label <str>]"
    echo "       [--thread-tag <tag>] [--no-notify] [--no-exclude] <A_old> <B_new>"
    echo "  action=live_sync 時 <A_old> <B_new> 可省略，會由 action 推導"
    echo "  其他情境下，A、B 目錄下應各有 ${COMPARE_PLATFORMS_SUBDIRS[*]} 等子目錄"
    exit 1
}

# ── 清理：TMPDIR 一定刪；LIVE_SYNC_CLEANUP_PATH 有安全檢查 ──
LIVE_SYNC_CLEANUP_PATH=""
_cleanup() {
    [[ -n "${TMPDIR:-}" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"
    if [[ -n "$LIVE_SYNC_CLEANUP_PATH" ]]; then
        _EXPECTED="/tmp/${GAME_TYPE:-__UNSET__}-TEST_SYNCTOLIVE_RESTORE"
        if [[ "$LIVE_SYNC_CLEANUP_PATH" == "$_EXPECTED" && -d "$LIVE_SYNC_CLEANUP_PATH" ]]; then
            rm -rf "$LIVE_SYNC_CLEANUP_PATH"
        fi
    fi
}
trap _cleanup EXIT

# 同時最多一個 instance — live_sync 模式特別需要保護 /tmp/${GAME_TYPE}-TEST_SYNCTOLIVE_RESTORE 互踩
# 也避免 cron + 手動 + TestCtrl 並行佔 CPU
LOCK_FILE="/tmp/compare_platforms_${GAME_TYPE}.lock"
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
    echo "❌ 另一個 compare_platforms ($GAME_TYPE) 正在執行中（lock: $LOCK_FILE），請等它跑完" >&2
    exit 1
fi

TMPDIR=$(mktemp -d -t compare_platforms.XXXXXX)

START_TIME=$(date +%s)
START_STR=$(date -d "@$START_TIME" '+%Y-%m-%d %H:%M:%S %Z')

# Log 檔固定放 www/download/ 下以便透過 URL 存取
mkdir -p "$COMPARE_PLATFORMS_DETAIL_OUTPUT_DIR"
LOG_FILENAME="compare_platforms_$(date -d "@$START_TIME" '+%Y%m%d_%H%M%S').log"
LOGFILE="$COMPARE_PLATFORMS_DETAIL_OUTPUT_DIR/$LOG_FILENAME"
LOG_URL="${COMPARE_PLATFORMS_DETAIL_BASE_URL%/}/$LOG_FILENAME"

exec > >(tee "$LOGFILE") 2>&1

# ── 依 action 推導預設值（手動參數優先於 action 推導）──
ACTION_TITLE="手動比對（Client Patch）"
case "$ACTION" in
    "test_update")
        ACTION_TITLE="測試機更新（test_update）"
        [[ -z "$THREAD_TAG" ]] && THREAD_TAG="test_patch"

        # A_LABEL 優先級：CLI 參數 > 狀態檔 > heuristic
        # 狀態檔：記錄上次 TestCtrl 成功安裝的版本
        _STATE_FILE="${COMPARE_PLATFORMS_STATE_DIR:-${WORKING_DIRECTORY:-$HOME}/patch/.compare_platforms_state}/test_update.last"
        if [[ -z "$A_LABEL" && -f "$_STATE_FILE" ]]; then
            A_LABEL=$(tr -d '[:space:]' < "$_STATE_FILE")
            [[ -n "$A_LABEL" ]] && echo "  (A_LABEL 來源: 狀態檔 $_STATE_FILE)" >&2
        fi

        # 掃 ~/patch/，只取「含有 client_* 檔案」且 dir 名符合 YYYYMMDD-HHMM 的目錄
        # （server-only / symbols-only patch 沒有 client_*，不算 client patch）
        _PATCH_DIRS=()
        while IFS= read -r _d; do
            _PATCH_DIRS+=("$_d")
        done < <(
            find "$HOME/patch/" -maxdepth 2 -name 'client_*' -type f 2>/dev/null \
                | awk -F/ '{print $(NF-1)}' \
                | grep -E '^[0-9]{8}-[0-9]{4}$' \
                | sort -u
        )
        if (( ${#_PATCH_DIRS[@]} >= 2 )); then
            [[ -z "$A_LABEL" ]] && { A_LABEL="${_PATCH_DIRS[-2]}"; echo "  (A_LABEL 來源: heuristic ~/patch/ 含 client_* 的倒數第二)" >&2; }
            [[ -z "$B_LABEL" ]] && B_LABEL="${_PATCH_DIRS[-1]}"
        elif (( ${#_PATCH_DIRS[@]} == 1 )); then
            [[ -z "$B_LABEL" ]] && B_LABEL="${_PATCH_DIRS[0]}"
            [[ -z "$A_LABEL" ]] && echo "⚠ action='test_update' 但 ~/patch/ 只有 1 個 client 版本目錄且無狀態檔，A_LABEL 未知" >&2
        else
            echo "⚠ action='test_update' 但 ~/patch/ 下無含 client_* 的 YYYYMMDD-HHMM 目錄，label 未知" >&2
        fi
        ;;
    "live_sync")
        ACTION_TITLE="正式機同步（live_sync）"
        [[ -z "$THREAD_TAG" ]] && THREAD_TAG="client_sync"
        (( NO_EXCLUDE_EXPLICIT == 0 )) && NO_EXCLUDE=1

        # A/B 預設（未傳 positional 時自動填）
        : "${WORKING_DIRECTORY:?live_sync 需要 .gamerc 提供 WORKING_DIRECTORY}"
        : "${GAME_TYPE:?live_sync 需要 .gamerc 提供 GAME_TYPE}"
        _DEFAULT_A="$WORKING_DIRECTORY/www/${GAME_TYPE}-TEST_SYNCTOLIVE"
        _DEFAULT_B="$WORKING_DIRECTORY/www/${GAME_TYPE}-TEST"
        [[ -z "$A" ]] && A="$_DEFAULT_A"
        [[ -z "$B" ]] && B="$_DEFAULT_B"

        # label 用「原 A 目錄名稱」vs「B 目錄名稱」
        [[ -z "$A_LABEL" ]] && A_LABEL="$(basename "$A")"
        [[ -z "$B_LABEL" ]] && B_LABEL="$(basename "$B")"

        # ── 前置作業：依 setup.txt 的 NFSExtension 決定是否需「複製去後綴」後再比 ──
        _RESTORE_DIR="/tmp/${GAME_TYPE}-TEST_SYNCTOLIVE_RESTORE"

        if [[ ! -d "$A" ]]; then
            # 問題1) 首發：無前版（SYNCTOLIVE 尚未建立）→ A 視為空，B 全部計為新增
            # 不建任何目錄；後段 scan_dir 對不存在目錄回傳空（視為空）
            echo "[live_sync prep] A 來源目錄不存在(首發,無前版): $A → 視為空，B 全部計為新增"
            EMPTY_A=1
            A_LABEL="(首發・無前版)"
        else
            # 讀原 A 的 setup.txt 取 NFSExtension（帶點與否由檔案內容決定）
            _SETUP_FILE="$A/setup.txt"
            [[ -f "$_SETUP_FILE" ]] || { echo "❌ setup.txt 不存在: $_SETUP_FILE" >&2; exit 1; }
            _NFS_EXT=$(grep -E '^NFSExtension=' "$_SETUP_FILE" | head -1 | cut -d= -f2- | tr -d '\r\n' | sed 's/[[:space:]]*$//')

            if [[ -z "$_NFS_EXT" ]]; then
                # 問題2) 無尾綴設計（setup.txt 無 NFSExtension）→ A、B 檔名本就一致
                # 不複製、不改名，直接比原始 A（省一次整包複製）
                echo "[live_sync prep] setup.txt 無 NFSExtension（無尾綴設計）→ 不複製、不改名，直接比對 $A"
            else
                echo "[live_sync prep] NFSExtension='$_NFS_EXT'"

                # 清除舊暫存（若存在）— 以 prefix 檢查避免誤刪
                if [[ -d "$_RESTORE_DIR" && "$_RESTORE_DIR" == "/tmp/${GAME_TYPE}-TEST_SYNCTOLIVE_RESTORE" ]]; then
                    echo "[live_sync prep] 清除既有暫存: $_RESTORE_DIR"
                    rm -rf "$_RESTORE_DIR"
                fi

                echo "[live_sync prep] 複製 $A → $_RESTORE_DIR"
                cp -a "$A" "$_RESTORE_DIR" || { echo "❌ 複製失敗" >&2; exit 1; }

                # 掃所有以 $_NFS_EXT 結尾的「檔案」，去掉結尾改名（衝突覆蓋）
                echo "[live_sync prep] 掃描並改名 *${_NFS_EXT} → 去掉結尾..."
                _RENAMED=0
                while IFS= read -r -d '' _f; do
                    _new="${_f%$_NFS_EXT}"
                    [[ "$_new" == "$_f" ]] && continue
                    mv -f "$_f" "$_new" && _RENAMED=$((_RENAMED + 1))
                done < <(find "$_RESTORE_DIR" -type f -name "*${_NFS_EXT}" -print0)
                echo "[live_sync prep] 已改名 $_RENAMED 個檔案"

                # A 改指向處理後的 restore 目錄；標記 compare 跑完需刪
                A="$_RESTORE_DIR"
                LIVE_SYNC_CLEANUP_PATH="$_RESTORE_DIR"
            fi
        fi
        ;;
    "")
        # 沒指定 action → 維持 CLI 原行為
        :
        ;;
    *)
        echo "⚠ 未知的 action: '$ACTION'，將用預設 title='Client Patch'" >&2
        ;;
esac

# ── 驗證（action 處理完後）──
[[ -n "$A" && -n "$B" ]] || usage
# 首發(EMPTY_A=1)時 A 不存在屬正常，scan_dir 會視為空；其餘情況 A 仍須存在
(( EMPTY_A == 0 )) && { [[ -d "$A" ]] || { echo "❌ A 目錄不存在: $A" >&2; exit 1; }; }
[[ -d "$B" ]] || { echo "❌ B 目錄不存在: $B" >&2; exit 1; }

# 組顯示字串：label 有帶就優先顯示 label + 路徑括號；沒有就只顯示路徑
if [[ -n "$A_LABEL" ]]; then
    A_DISPLAY="$A_LABEL ($A)"
else
    A_DISPLAY="$A"
fi
if [[ -n "$B_LABEL" ]]; then
    B_DISPLAY="$B_LABEL ($B)"
else
    B_DISPLAY="$B"
fi

# ── helpers ──

human_duration() {
    local total="$1"
    local h=$((total / 3600))
    local m=$(((total % 3600) / 60))
    local s=$((total % 60))
    if (( h > 0 )); then
        printf '%dh %dm %ds' "$h" "$m" "$s"
    elif (( m > 0 )); then
        printf '%dm %ds' "$m" "$s"
    else
        printf '%ds' "$s"
    fi
}

human_size() {
    awk -v b="$1" 'BEGIN {
        if (b < 1024)             printf "%d B",    b
        else if (b < 1048576)     printf "%.1f KB", b/1024
        else if (b < 1073741824)  printf "%.1f MB", b/1048576
        else                      printf "%.1f GB", b/1073741824
    }'
}

# 顯示寬度：ASCII 算 1，非 ASCII（中文/全形）算 2
# 注意：local s w n 不能寫一行同時賦值（bash 多重 local 對 ${#s} 的展開時機有坑會抓到空字串）
# 判斷 ASCII 不用 regex（[[:ascii:]] / 區段都會誤判中文為 ASCII），改看 UTF-8 byte length
disp_width() {
    local s="$1"
    local n=${#s}
    local w=0 i blen
    for ((i=0; i<n; i++)); do
        local c="${s:$i:1}"
        blen=$(LC_ALL=C printf '%s' "$c" | wc -c)
        if (( blen > 1 )); then
            (( w += 2 ))
        else
            (( w += 1 ))
        fi
    done
    echo "$w"
}

# 依顯示寬度補空格；用法：mb_pad <text> <target_width> [l|r]（預設 r 右對齊）
mb_pad() {
    local s="$1" target="$2" align="${3:-r}"
    local w
    w=$(disp_width "$s")
    local pad=$((target - w))
    (( pad < 0 )) && pad=0
    if [[ "$align" == "l" ]]; then
        printf '%s%*s' "$s" "$pad" ''
    else
        printf '%*s%s' "$pad" '' "$s"
    fi
}

scan_dir() {
    local dir="$1"
    [[ -d "$dir" ]] || return 0

    local prune_args=()
    if (( NO_EXCLUDE == 0 )); then
        for excl in "${COMPARE_PLATFORMS_EXCLUDE_PATHS[@]}"; do
            prune_args+=(-path "./${excl}" -prune -o)
        done
    fi

    # 並行 md5sum + 一次性 find -printf 收 size，避免 per-file fork（原 stat+md5sum+cut 每檔三 fork）
    # 同 dir 內 600 檔 4m → ~30s
    local sizes_tmp md5s_tmp err_find err_md5
    sizes_tmp=$(mktemp -p "$TMPDIR" sizes.XXXXXX)
    md5s_tmp=$(mktemp -p "$TMPDIR" md5s.XXXXXX)
    err_find=$(mktemp -p "$TMPDIR" err_find.XXXXXX)
    err_md5=$(mktemp -p "$TMPDIR" err_md5.XXXXXX)

    # find -printf '%P\t%s\n' 直接吐 path<TAB>size，無 fork
    (cd "$dir" && find . "${prune_args[@]}" -type f -printf '%P\t%s\n' 2>"$err_find") > "$sizes_tmp"

    # xargs -P N -n 200 並行算 md5；md5sum 輸出 "<32hex>  ./<path>"
    (cd "$dir" && find . "${prune_args[@]}" -type f -print0 2>>"$err_find" \
        | xargs -0 -r -P "${COMPARE_PLATFORMS_PARALLEL_JOBS:-8}" -n 200 md5sum 2>"$err_md5" \
        | awk '{md5=$1; sub(/^[^ ]+ +\.\//, ""); print $0 "\t" md5}') > "$md5s_tmp"

    # 失敗檔案 trace（broken symlink、permission denied 等不再靜默吃掉）
    local err_total=0
    if [[ -s "$err_find" ]]; then
        local n
        n=$(wc -l < "$err_find")
        err_total=$((err_total + n))
        echo "  ⚠ scan($dir): find/stat 失敗 $n 條（前 3 條）" >&2
        head -3 "$err_find" | sed 's/^/    /' >&2
    fi
    if [[ -s "$err_md5" ]]; then
        local n
        n=$(wc -l < "$err_md5")
        err_total=$((err_total + n))
        echo "  ⚠ scan($dir): md5sum 失敗 $n 條（前 3 條）" >&2
        head -3 "$err_md5" | sed 's/^/    /' >&2
    fi

    # 兩份用 path key join：輸出 path<TAB>size<TAB>md5（順序不重要，後段 awk 用 hash 比對）
    LC_ALL=C awk -F'\t' 'NR==FNR{size[$1]=$2; next} ($1 in size){print $1 "\t" size[$1] "\t" $2}' \
        "$sizes_tmp" "$md5s_tmp"

    rm -f "$sizes_tmp" "$md5s_tmp" "$err_find" "$err_md5"
}

compare_lists() {
    local a_file="$1"
    local b_file="$2"
    local out_prefix="$3"

    : > "${out_prefix}.only_A"
    : > "${out_prefix}.only_B"
    : > "${out_prefix}.diff"

    awk -F'\t' -v oa="${out_prefix}.only_A" \
                -v ob="${out_prefix}.only_B" \
                -v od="${out_prefix}.diff" \
                -v a_file="$a_file" '
        FILENAME == a_file {
            A_size[$1] = $2
            A_md5[$1]  = $3
            next
        }
        {
            B_size[$1] = $2
            B_md5[$1]  = $3
        }
        END {
            only_a_count=0; only_a_size=0
            only_b_count=0; only_b_size=0
            diff_count=0;   diff_size=0

            for (p in A_md5) {
                if (!(p in B_md5)) {
                    print p "\t" A_size[p] > oa
                    only_a_count++
                    only_a_size += A_size[p]
                } else if (A_md5[p] != B_md5[p]) {
                    d = B_size[p]
                    print p "\t" A_size[p] "\t" B_size[p] "\t" d > od
                    diff_count++
                    diff_size += d
                }
            }
            for (p in B_md5) {
                if (!(p in A_md5)) {
                    print p "\t" B_size[p] > ob
                    only_b_count++
                    only_b_size += B_size[p]
                }
            }
            printf "%d\t%d\t%d\t%d\t%d\t%d\n", \
                only_a_count, only_a_size, \
                only_b_count, only_b_size, \
                diff_count,   diff_size
        }
    ' "$a_file" "$b_file"

    # 排序輸出檔，確保同一 patch 兩次跑出 list 順序相同（awk for-in 順序不保證）
    LC_ALL=C sort -o "${out_prefix}.only_A" "${out_prefix}.only_A"
    LC_ALL=C sort -o "${out_prefix}.only_B" "${out_prefix}.only_B"
    LC_ALL=C sort -o "${out_prefix}.diff"   "${out_prefix}.diff"
}

# ── 發 Google Chat 通知 ──
#   優先序：1. --no-notify → 不發
#          2. --thread-tag <tag> → send_chatbot_text_only_by_thread_tag
#          3. 否則直接 POST 到 GOOGLE_CHAT_WEBHOOK_URL
send_notification() {
    local message="$1"

    if (( DO_NOTIFY == 0 )); then
        echo "[notify] --no-notify，跳過通知"
        return 0
    fi

    if [[ -n "$THREAD_TAG" ]]; then
        local chatbot_script="$HOME/bin/send_chatbot_text_only_by_thread_tag"
        if [[ ! -x "$chatbot_script" ]]; then
            echo "[notify] ⚠ $chatbot_script 不存在或不可執行，跳過 thread 通知" >&2
            return 1
        fi
        "$chatbot_script" "$THREAD_TAG" "網頁工具操作" "$message" minute 10
        echo "[notify] 已透過 thread_tag=$THREAD_TAG 發送通知"
        return 0
    fi

    local webhook="${GOOGLE_CHAT_WEBHOOK_URL:-}"
    if [[ -z "$webhook" ]]; then
        echo "[notify] ⚠ GOOGLE_CHAT_WEBHOOK_URL 未設定，跳過通知" >&2
        return 1
    fi

    local json_payload
    json_payload="$(
        /usr/bin/python3 -c 'import json,sys; print(json.dumps({"text": sys.stdin.read()}))' <<< "$message"
    )"

    if /usr/bin/curl -fsS \
        -X POST \
        -H 'Content-Type: application/json; charset=UTF-8' \
        -d "$json_payload" \
        "$webhook" > /dev/null; then
        echo "[notify] 已透過 GOOGLE_CHAT_WEBHOOK_URL 發送通知"
    else
        echo "[notify] ⚠ 通知發送失敗" >&2
        return 1
    fi
}

# ── 主流程 ──

PLATFORMS=("${COMPARE_PLATFORMS_SUBDIRS[@]}")

echo "╔══════════════════════════════════════════════════════════╗"
echo "║           目錄差異比對 (md5 content comparison)          ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo "A (舊): $A_DISPLAY"
echo "B (新): $B_DISPLAY"
echo "開始時間: $START_STR"
echo "Log 檔案: $LOGFILE"
echo "Log URL:  $LOG_URL"
echo "通知模式: $(
    if (( DO_NOTIFY == 0 )); then
        echo "--no-notify (不發)"
    elif [[ -n "$THREAD_TAG" ]]; then
        echo "thread_tag=$THREAD_TAG"
    else
        echo "GOOGLE_CHAT_WEBHOOK_URL"
    fi
)"
echo "Exclude: $(
    if (( NO_EXCLUDE == 1 )); then
        echo "(無，全比)"
    else
        echo "${COMPARE_PLATFORMS_EXCLUDE_PATHS[*]}"
    fi
)"
echo ""
echo "掃描並計算 md5 中..."

for p in "${PLATFORMS[@]}"; do
    [[ -d "$A/$p" ]] || echo "  ⚠ $A/$p 不存在，視為空" >&2
    [[ -d "$B/$p" ]] || echo "  ⚠ $B/$p 不存在，視為空" >&2
    echo "  [$p] 掃描 A..."
    scan_dir "$A/$p" > "$TMPDIR/$p.A"
    echo "  [$p]   A 掃了 $(wc -l < "$TMPDIR/$p.A") 個檔"
    echo "  [$p] 掃描 B..."
    scan_dir "$B/$p" > "$TMPDIR/$p.B"
    echo "  [$p]   B 掃了 $(wc -l < "$TMPDIR/$p.B") 個檔"
done

# Warning 門檻（-1 停用該項；未設時用極大值即無作用）
_WARN_SIZE="${COMPARE_PLATFORMS_WARNING_SIZE_CRITERIA:-999999999999}"
_WARN_NUM="${COMPARE_PLATFORMS_WARNING_NUMBER_CRITERIA:-999999999999}"

# 組 criteria 顯示字串
if (( _WARN_NUM < 0 )) && (( _WARN_SIZE < 0 )); then
    CRITERIA_LINE="皆停用，永遠 ✅"
else
    _num_part="數量：停用"
    (( _WARN_NUM >= 0 )) && _num_part="數量 ≤ $_WARN_NUM"
    _size_part="大小：停用"
    (( _WARN_SIZE >= 0 )) && _size_part="大小 ≤ $(human_size $_WARN_SIZE)"
    CRITERIA_LINE="${_num_part}, ${_size_part}（超過任一 → ❌）"
fi

echo ""
echo "============================================================"
echo "統計結果"
echo "A (舊): $A_DISPLAY"
echo "B (新): $B_DISPLAY"
echo "Warning 門檻: $CRITERIA_LINE"
echo "============================================================"
printf "%s %s %s %s %s %s %s\n" \
    "$(mb_pad '' 2 l)" \
    "$(mb_pad 'Platform' 10 l)" \
    "$(mb_pad 'A獨有' 10 r)" \
    "$(mb_pad 'B獨有' 10 r)" \
    "$(mb_pad '內容不同' 10 r)" \
    "$(mb_pad '差異總數' 12 r)" \
    "$(mb_pad '差異大小' 15 r)"
printf -- "-------------------------------------------------------------------------\n"

TOTAL_OA=0; TOTAL_OB=0; TOTAL_DC=0; TOTAL_SIZE=0
declare -a PLATFORM_ROWS=()
declare -a PLATFORM_ROWS_FOR_CHAT=()
ANY_EXCEEDED=0

for p in "${PLATFORMS[@]}"; do
    stats=$(compare_lists "$TMPDIR/$p.A" "$TMPDIR/$p.B" "$TMPDIR/$p")
    IFS=$'\t' read -r oa_c oa_s ob_c ob_s d_c d_s <<< "$stats"
    pt_count=$((oa_c + ob_c + d_c))
    # 差異大小只計 B 獨有 + 內容不同（A 獨有不計入，因為新版不需下載）
    pt_size=$((ob_s + d_s))
    pt_size_h=$(human_size $pt_size)

    pt_exceeded=0
    (( _WARN_NUM >= 0 )) && (( pt_count > _WARN_NUM )) && pt_exceeded=1
    (( _WARN_SIZE >= 0 )) && (( pt_size > _WARN_SIZE )) && pt_exceeded=1
    if (( pt_exceeded == 1 )); then
        pt_icon="❌"
        ANY_EXCEEDED=1
    else
        pt_icon="✅"
    fi

    printf "%s %s %10d %10d %10d %12d %15s\n" \
        "$(mb_pad "$pt_icon" 2 l)" "$(mb_pad "$p" 10 l)" "$oa_c" "$ob_c" "$d_c" "$pt_count" "$pt_size_h"
    PLATFORM_ROWS_FOR_CHAT+=("$(printf '%s %-10s A獨有:%-4d B獨有:%-4d 內容不同:%-4d 差異:%s' "$pt_icon" "$p" "$oa_c" "$ob_c" "$d_c" "$pt_size_h")")
    TOTAL_OA=$((TOTAL_OA + oa_c))
    TOTAL_OB=$((TOTAL_OB + ob_c))
    TOTAL_DC=$((TOTAL_DC + d_c))
    TOTAL_SIZE=$((TOTAL_SIZE + pt_size))
done
printf -- "-------------------------------------------------------------------------\n"
GRAND_TOTAL=$((TOTAL_OA + TOTAL_OB + TOTAL_DC))
TOTAL_SIZE_H=$(human_size $TOTAL_SIZE)

# 總計列的 icon：與訊息內「超過任一 → ❌」語意一致
# 採用 ANY_EXCEEDED（per-platform 任一超標即 ❌），不再對 total 重套門檻
# Why: 同一門檻同時套 per-platform 又套 sum 會讓「每個平台都過、加起來必然爆」變誤判
if (( ANY_EXCEEDED == 1 )); then
    TOTAL_ICON="❌"
else
    TOTAL_ICON="✅"
fi

printf "%s %s %10d %10d %10d %12d %15s\n" \
    "$(mb_pad "$TOTAL_ICON" 2 l)" "$(mb_pad '總計' 10 l)" "$TOTAL_OA" "$TOTAL_OB" "$TOTAL_DC" \
    "$GRAND_TOTAL" "$TOTAL_SIZE_H"

echo ""
echo "============================================================"
echo "細項"
echo "============================================================"

for p in "${PLATFORMS[@]}"; do
    echo ""
    echo "━━━ $p ━━━"
    any_output=0

    if [[ -s "$TMPDIR/$p.only_A" ]]; then
        echo ""
        echo "▶ A 獨有 ($(wc -l < "$TMPDIR/$p.only_A") 個):"
        while IFS=$'\t' read -r path size; do
            printf "  [%s] %s\n" "$(human_size $size)" "$path"
        done < "$TMPDIR/$p.only_A"
        any_output=1
    fi

    if [[ -s "$TMPDIR/$p.only_B" ]]; then
        echo ""
        echo "▶ B 獨有 ($(wc -l < "$TMPDIR/$p.only_B") 個):"
        while IFS=$'\t' read -r path size; do
            printf "  [%s] %s\n" "$(human_size $size)" "$path"
        done < "$TMPDIR/$p.only_B"
        any_output=1
    fi

    if [[ -s "$TMPDIR/$p.diff" ]]; then
        echo ""
        echo "▶ 內容不同 ($(wc -l < "$TMPDIR/$p.diff") 個):"
        while IFS=$'\t' read -r path size_a size_b diff; do
            printf "  [新:%s] %s (A:%s → B:%s)\n" \
                "$(human_size $diff)" "$path" \
                "$(human_size $size_a)" "$(human_size $size_b)"
        done < "$TMPDIR/$p.diff"
        any_output=1
    fi

    [[ "$any_output" -eq 0 ]] && echo "  (無差異)"
done

echo ""
END_TIME=$(date +%s)
END_STR=$(date -d "@$END_TIME" '+%Y-%m-%d %H:%M:%S %Z')
ELAPSED=$((END_TIME - START_TIME))
ELAPSED_H=$(human_duration $ELAPSED)

echo "============================================================"
echo "A (舊):    $A_DISPLAY"
echo "B (新):    $B_DISPLAY"
echo "開始時間: $START_STR"
echo "結束時間: $END_STR"
echo "總計耗用: $ELAPSED_H"
echo "Log 檔案: $LOGFILE"
echo "Log URL:  $LOG_URL"
echo "============================================================"

# machine-readable summary（方便呼叫者 grep）
echo ""
echo "[SUMMARY] total=$GRAND_TOTAL size_bytes=$TOTAL_SIZE size_human=$TOTAL_SIZE_H log_url=$LOG_URL"

# ── 組通知訊息 + 發送 ──

# 整體狀態 icon：有任一超過門檻 → ❌；全都在門檻內 → ✅
if (( ANY_EXCEEDED == 1 )); then
    OVERALL_ICON="❌"
else
    OVERALL_ICON="✅"
fi
TITLE="${OVERALL_ICON} [${GAME_TYPE}-${COUNTRY_CODE_DISPLAY}] ${ACTION_TITLE} 差異比對結果"

if (( GRAND_TOTAL == 0 )); then
    CHAT_MESSAGE="$(cat <<EOF
$TITLE
$START_STR
A (舊): $A_DISPLAY
B (新): $B_DISPLAY
兩個目錄完全一致（扣除 ${COMPARE_PLATFORMS_EXCLUDE_PATHS[*]}）
執行耗時: $ELAPSED_H
Warning 門檻: $CRITERIA_LINE
Log: $LOG_URL
EOF
)"
else
    # 組 per-platform 列
    TABLE_ROWS=""
    for row in "${PLATFORM_ROWS_FOR_CHAT[@]}"; do
        TABLE_ROWS+="${row}"$'\n'
    done
    CHAT_MESSAGE="$(cat <<EOF
$TITLE
$START_STR
A (舊): $A_DISPLAY
B (新): $B_DISPLAY
總計: $GRAND_TOTAL 個差異 / $TOTAL_SIZE_H
執行耗時: $ELAPSED_H
Warning 門檻: $CRITERIA_LINE
Log: $LOG_URL

$TABLE_ROWS
EOF
)"
fi

echo ""
send_notification "$CHAT_MESSAGE" || echo "[notify] 通知發送未成功（不影響 exit code）" >&2

# 確保 tee 把最後的內容寫完
exec >&- 2>&-
wait
