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

########################################
# Usage / 參數
#
# $1: ENVIRONMENT (test|live)
# $2: FILE_NAME
# $3: MODULE
# $4..: [Sxx ...] [pre|post|both]
#
# SetID filter:
#   - 預設不加：針對檔案內所有 SetID 全部處理
#   - 加 S11 / S21 ...：僅處理指定 SetID（可多個）
#
# removebackup scope:
#   - pre | post | both（預設 both）
#
# 單一基礎功能 (single module, echo):
#   generate                : echo 產生合併所需的相關工具與檔案
#   reward_before_merge     : echo 發送獎勵 fastcgi
#   dbcheck                 : echo DB 連線檢查
#   prebackup               : echo 合併前 DB 備份
#   restore_prebackup       : echo 由 Pre 備份還原
#   merge_rollback_partial_worlds : echo rollback partial worlds
#   merge                   : echo merge_exec
#   logcheck                : echo 初次檢查 log
#   waitdone                : echo 等待 Done 的檢查指令
#   update_guild_race_data  : echo 更新/修正 guild race data
#   update_real_world_id_for_source_wids_on_fake_worlds
#                          : echo 檢查/修正 AccountDB fake_worlds.real_world_id（避免 source world ids 殘留）
#   postbackup              : echo 合併後 DB 備份
#   check                   : echo merge_check
#   update_db_setup         : echo update_db_setup_for_merge（僅 live；test 只註解不生指令）
#   move_old_server_directory    : echo move_source_wids_directory_after_merge
#   remove_source_wids_on_hosts  : echo remove_source_wids_on_hosts_after_merge（僅 live；test 只註解不生指令）
#   apply_hosts_new_and_sync2live_on_test
#                          : echo merge_apply_hosts_new_and_sync2live_on_test（僅 live；test 只註解不生指令）
#   removebackup            : echo 刪除備份 DB（test 也 echo；依原本 echo 風格維持簡潔）
#   removebackup_wb         : echo 刪除備份 DB（備份=1）
#   removebackup_wob        : echo 刪除備份 DB（不備份=0）
#
# 新增檢查模組 (single module, echo):
#   check_dbsetup_modification       : echo 檢查 db_setup 合併結果（僅 live；test 只註解不生指令）
#   check_source_wids_directory      : echo 檢查 source world ids 目錄移除（test/live）
#   check_source_wids_on_hosts       : echo 檢查 hosts 移除（僅 live；test 只註解不生指令）
#   check_worlds_related_modification: echo 檢查 worlds & fake_worlds 合併結果（test/live）
#
# 群組功能 (group module, echo) 與組成:
#   exec_merge  =
#       generate
#       + dbcheck
#       + prebackup
#       + merge
#       + logcheck
#       + waitdone
#       + update_guild_race_data
#       + update_real_world_id_for_source_wids_on_fake_worlds
#       + postbackup
#       + check
#
#   before_merge =
#       generate
#       + reward_before_merge
#
#   after_merge (自動依 ENV 分流) =
#       (ENV=live) after_merge_live
#       (ENV=test) after_merge_test
#
#   after_merge_live =
#       update_db_setup
#       + move_old_server_directory
#       + remove_source_wids_on_hosts
#       + apply_hosts_new_and_sync2live_on_test
#
#   after_merge_test =
#       move_old_server_directory
#
#   after_merge_verify (自動依 ENV 分流) =
#       (ENV=live) after_merge_verify_live
#       (ENV=test) after_merge_verify_test
#
#   after_merge_verify_live =
#       check_dbsetup_modification
#       + check_source_wids_directory
#       + check_source_wids_on_hosts
#       + check_worlds_related_modification
#
#   after_merge_verify_test =
#       check_source_wids_directory
#       + check_worlds_related_modification
#
# 其他群組（維持原樣）:
#   end_merge / re_run_* ...（同 run_merge_n1_echo 原本定義）
########################################

print_usage_and_exit() {
    echo "Usage: $0 <ENVIRONMENT: test|live> <FILE_NAME> [MODULE] [Sxx ...] [pre|post|both]" >&2
    echo "  SetID filter:" >&2
    echo "    - 預設不加：針對檔案內所有 SetID 全部處理" >&2
    echo "    - 加 S11 / S21 ...：僅處理指定 SetID（可多個）" >&2
    echo "  removebackup scope:" >&2
    echo "    - pre | post | both（預設 both）" >&2
    echo "" >&2

    echo "  單一基礎功能 (single):" >&2
    echo "    generate" >&2
    echo "      - 產生合併所需的相關工具與檔案 (e.g. merge_setup.ini / MergeTool / merge_exec )" >&2
    echo "      - 呼叫: \$HOME/bin/generate_merge_preparation or generate_merge_preparation_test <DIRECTORY_NAME> <MERGE_WORLD_ID_LISTS: TARGET_WID SOURCE_WID_LISTS>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    reward_before_merge" >&2
    echo "      - 針對合併伺服器的 source world ids 執行發獎 CGI reward_activity+\${source_wid}+5" >&2
    echo "      - 呼叫: \$HOME/bin/send_fastcgi_all_once_n1_by_world_reward_merge <ENVIRONMENT: test|live> <TARGET_WID> <SOURCE_WID_LISTS>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    dbcheck" >&2
    echo "      - 針對合併伺服器 merge_setup.ini 內的所有 DB 相關連線是否正常" >&2
    echo "      - 呼叫: \$HOME/bin/merge_db_connect_verify <MERGE_SETUP_INI>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    prebackup" >&2
    echo "      - 針對合併伺服器前的 AccountDB/GameDB/WorldDB 做 DB 備份" >&2
    echo "      - 呼叫: \$HOME/bin/merge_db_backup <MERGE_SETUP_INI> <DB_NAME_POSTFIX> {AccountDB/GameDB/WorldDB}" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    restore_prebackup" >&2
    echo "      - 針對合併伺服器的 AccountDB/GameDB/WorldDB 做 DB 還原" >&2
    echo "      - 呼叫: \$HOME/bin/merge_db_restore <MERGE_SETUP_INI> <DB_NAME_POSTFIX> {AccountDB/GameDB/WorldDB}" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    merge_rollback_partial_worlds" >&2
    echo "      - 針對合併伺服器的 AccountDB 還原 worlds & fake_worlds" >&2
    echo "      - 呼叫: \$HOME/bin/merge_rollback_partial_worlds <MERGE_SETUP_INI> <BACKUP_TAG>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    merge" >&2
    echo "      - 執行遊戲 GameDBServer 合併 DB 程序" >&2
    echo "      - 呼叫: 配合合併目錄下的 <MERGE_SETUP_INI> 執行 merge_exec 程序" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    logcheck" >&2
    echo "      - 確認合併執行流程的 Log 檢查" >&2
    echo "      - 搜尋: 合併目錄下的 Test.log* (,WolrdDB|,WorldDB|,GameDB|,AccountDB|,Done| autosend_info| redenvelope_info)" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    waitdone" >&2
    echo "      - 確認合併程序完成的 Log 檢查" >&2
    echo "      - 搜尋: 合併目錄下的 Test.log* (,Done)" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    update_guild_race_data" >&2
    echo "      - 檢查 GameDB 公會戰相關資料表的合併結果, 如果合併程式修改 source world ids 失敗, 則腳本協助修改" >&2
    echo "      - 呼叫: \$HOME/bin/merge_update_guild_race_data <MERGE_SETUP_INI>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    update_real_world_id_for_source_wids_on_fake_worlds" >&2
    echo "      - 檢查 AccountDB > fake_worlds, 確認 source world ids 有沒有存在 real_world_id 欄位中, 如果合併程式修改 source world ids 失敗, 則腳本協助修改" >&2
    echo "      - 呼叫: \$HOME/bin/merge_update_real_world_id_for_source_wids_on_fake_worlds <MERGE_SETUP_INI>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    postbackup" >&2
    echo "      - 針對合併伺服器後的 AccountDB/GameDB/WorldDB 做 DB 備份" >&2
    echo "      - 呼叫: \$HOME/bin/merge_db_backup <MERGE_SETUP_INI> <DB_NAME_POSTFIX> {AccountDB/GameDB/WorldDB}" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    check" >&2
    echo "      - 針對合併伺服器前後的 GameDB, 確認所有合併相關資料表, 都有如預期處理完成" >&2
    echo "      - 呼叫: \$HOME/bin/merge_check <MERGE_SETUP_INI> <DB_NAME_POSTFIX_PRE> <DB_NAME_POSTFIX_POST> <ENVIRONMENT: test|live>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    update_db_setup" >&2
    echo "      - 合併伺服器後, 針對所有 source world ids 的 db_setup 資料, 轉換為 target world id 的" >&2
    echo "      - 呼叫: \$HOME/bin/update_db_setup_for_merge <TARGET_WID> <SOURCE_WID_LISTS>" >&2
    echo "      - 僅適用: live 正式合併" >&2

    echo "    move_old_server_directory" >&2
    echo "      - 合併伺服器後, 移除所有 source world ids 的相關 Game Servers 目錄" >&2
    echo "      - 呼叫: \$HOME/bin/move_source_wids_directory_after_merge <ENVIRONMENT: test|live> <DESTINATION_DIRECTORY> <TARGET_WID> <SOURCE_WID_LISTS>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    remove_source_wids_on_hosts" >&2
    echo "      - 合併伺服器後, 移除所有 source world ids 的相關 hosts 的 Game Servers 資料" >&2
    echo "      - 呼叫: \$HOME/bin/remove_source_wids_on_hosts_after_merge <TARGET_WID> <SOURCE_WID_LISTS>" >&2
    echo "      - 僅適用: live 正式合併" >&2

    echo "    apply_hosts_new_and_sync2live_on_test" >&2
    echo "      - 合併伺服器後, 產生 hosts.new 的資料後, 重新更新 hosts 到測試機, 並同步到正式環境" >&2
    echo "      - 呼叫: \$HOME/bin/merge_apply_hosts_new_and_sync2live_on_test" >&2
    echo "      - 僅適用: live 正式合併" >&2

    echo "    check_dbsetup_modification" >&2
    echo "      - 檢查合併後 db_setup 資料表 source world ids 整併到 target world id 的結果是否正確" >&2
    echo "      - 呼叫: \$HOME/bin/check_dbsetup_modification_after_merge <MERGE_FILE_NAME>" >&2
    echo "      - 僅適用: live 正式合併" >&2

    echo "    check_source_wids_directory" >&2
    echo "      - 檢查合併後 source world ids 相關的 Game Servers 目錄移除是否正確" >&2
    echo "      - 呼叫: \$HOME/bin/check_source_wids_directory_after_merge <ENVIRONMENT: test|live> <MERGE_FILE_NAME>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    check_source_wids_on_hosts" >&2
    echo "      - 檢查合併後 hosts 是否移除不需要的 source world ids 相關的 Game Servers 資料" >&2
    echo "      - 呼叫: \$HOME/bin/check_source_wids_on_hosts_after_merge <MERGE_FILE_NAME>" >&2
    echo "      - 僅適用: live 正式合併" >&2

    echo "    check_worlds_related_modification" >&2
    echo "      - 檢查合併後 worlds & fake_worlds 資料表整併的結果是否正確" >&2
    echo "      - 呼叫: \$HOME/bin/check_worlds_related_modification_after_merge <ENVIRONMENT: test|live> <MERGE_FILE_NAME>" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    removebackup" >&2
    echo "      - 刪除合併伺服器各機器, 特定 pattern 的資料庫 (主要就是備份 DB), 備份與否可自行決定" >&2
    echo "      - 呼叫: \$HOME/bin/drop_database_for_current_machine_by_pattern <TYPE: PREFIX / POSTFIX / CONTAIN> <PATTERN> [BACKUP_FLAG: 1|0]" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    removebackup_wb" >&2
    echo "      - 刪除合併伺服器各機器, 特定 pattern 的資料庫 (主要就是備份 DB), 要備份" >&2
    echo "      - 呼叫: \$HOME/bin/drop_database_for_current_machine_by_pattern <TYPE: PREFIX / POSTFIX / CONTAIN> <PATTERN> [BACKUP_FLAG: 1]" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "    removebackup_wob" >&2
    echo "      - 刪除合併伺服器各機器, 特定 pattern 的資料庫 (主要就是備份 DB), 不備份" >&2
    echo "      - 呼叫: \$HOME/bin/drop_database_for_current_machine_by_pattern <TYPE: PREFIX / POSTFIX / CONTAIN> <PATTERN> [BACKUP_FLAG: 0]" >&2
    echo "      - 適用: test 測試合併 / live 正式合併" >&2

    echo "" >&2
    echo "  群組功能 (group):" >&2

    echo "    exec_merge  =" >&2
    echo "        generate" >&2
    echo "        + dbcheck" >&2
    echo "        + prebackup" >&2
    echo "        + merge" >&2
    echo "        + logcheck" >&2
    echo "        + waitdone" >&2
    echo "        + update_guild_race_data" >&2
    echo "        + update_real_world_id_for_source_wids_on_fake_worlds" >&2
    echo "        + postbackup" >&2
    echo "        + check" >&2
    echo "" >&2

    echo "    before_merge =" >&2
    echo "        generate" >&2
    echo "        + reward_before_merge" >&2
    echo "" >&2

    echo "    after_merge (auto) =" >&2
    echo "        (ENV=live) after_merge_live" >&2
    echo "        (ENV=test) after_merge_test" >&2
    echo "" >&2

    echo "    after_merge_live =" >&2
    echo "        update_db_setup" >&2
    echo "        + move_old_server_directory" >&2
    echo "        + remove_source_wids_on_hosts" >&2
    echo "        + apply_hosts_new_and_sync2live_on_test" >&2
    echo "" >&2

    echo "    after_merge_test =" >&2
    echo "        move_old_server_directory" >&2
    echo "" >&2

    echo "    after_merge_verify (auto) =" >&2
    echo "        (ENV=live) after_merge_verify_live" >&2
    echo "        (ENV=test) after_merge_verify_test" >&2
    echo "" >&2

    echo "    after_merge_verify_live =" >&2
    echo "        check_dbsetup_modification" >&2
    echo "        + check_source_wids_directory" >&2
    echo "        + check_source_wids_on_hosts" >&2
    echo "        + check_worlds_related_modification" >&2
    echo "" >&2

    echo "    after_merge_verify_test =" >&2
    echo "        check_source_wids_directory" >&2
    echo "        + check_worlds_related_modification" >&2

    exit 1
}

if [ "$#" -lt 2 ]; then
    print_usage_and_exit
fi

ENVIRONMENT="$1"
FILE_NAME="$2"
MODULE="${3:-exec_merge}"
shift 3 || true

case "$ENVIRONMENT" in
    test|live) ;;
    *)
        echo "Error: ENVIRONMENT must be 'test' or 'live', got '$ENVIRONMENT'." >&2
        exit 1
        ;;
esac

declare -a SET_FILTERS=()
REMOVE_SCOPE="both"

for arg in "$@"; do
    if [[ "$arg" =~ ^S([0-9]+)$ ]]; then
        SET_FILTERS+=( "${BASH_REMATCH[1]}" )
    elif [[ "$arg" == "pre" || "$arg" == "post" || "$arg" == "both" ]]; then
        REMOVE_SCOPE="$arg"
    else
        echo "Warning: ignored arg: $arg" >&2
    fi
done

if [ "${#SET_FILTERS[@]}" -gt 0 ]; then
    declare -A _seen_sid=()
    declare -a _uniq=()
    for sid in "${SET_FILTERS[@]}"; do
        if [[ -z "${_seen_sid[$sid]+x}" ]]; then
            _seen_sid["$sid"]=1
            _uniq+=( "$sid" )
        fi
    done
    SET_FILTERS=( "${_uniq[@]}" )
fi

INPUT_FILE="$HOME/bin/merge_n1/${ENVIRONMENT}/${FILE_NAME}"

if [ ! -f "$INPUT_FILE" ]; then
    echo "Error: input file not found: $INPUT_FILE" >&2
    exit 1
fi
if [ ! -s "$INPUT_FILE" ]; then
    echo "Error: input file is empty: $INPUT_FILE" >&2
    exit 1
fi

START_TS=$(date +%s)
START_TIME=$(date '+%Y-%m-%d %H:%M:%S')
echo "== run_merge_n1_echo start at ${START_TIME} (ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}, MODULE=${MODULE}) =="

declare -A list_worlds
declare -A list_setid
declare -A set_lists
declare -A set_first_list
declare -a all_lists

LINE_NO=0
while IFS= read -r line || [ -n "$line" ]; do
    LINE_NO=$((LINE_NO + 1))

    line_trimmed="${line#"${line%%[![:space:]]*}"}"
    line_trimmed="${line_trimmed%"${line_trimmed##*[![:space:]]}"}"

    if [ -z "$line_trimmed" ] || [[ "$line_trimmed" =~ ^# ]]; then
        continue
    fi

    read -r -a WORLDS <<< "$line_trimmed"
    if [ "${#WORLDS[@]}" -lt 1 ]; then
        echo "Error: line $LINE_NO has no WorldID: '$line'" >&2
        exit 1
    fi

    LINE_SET_ID=""
    for wid in "${WORLDS[@]}"; do
        if ! [[ "$wid" =~ ^[0-9]+$ ]]; then
            echo "Error: line $LINE_NO has non-numeric WorldID '$wid'." >&2
            exit 1
        fi
        wid_set_id=$(( wid / 100 ))
        if [ -z "$LINE_SET_ID" ]; then
            LINE_SET_ID="$wid_set_id"
        else
            if (( wid_set_id != LINE_SET_ID )); then
                echo "Error: line $LINE_NO has mixed SET_ID: expected $LINE_SET_ID, got $wid_set_id (WorldID: $wid)." >&2
                exit 1
            fi
        fi
    done

    LID="L${LINE_NO}"
    list_worlds["$LID"]="$line_trimmed"
    list_setid["$LID"]="$LINE_SET_ID"
    all_lists+=( "$LID" )

    if [[ -z "${set_lists[$LINE_SET_ID]+x}" ]]; then
        set_lists["$LINE_SET_ID"]="$LID"
        set_first_list["$LINE_SET_ID"]="$LID"
    else
        set_lists["$LINE_SET_ID"]+=" $LID"
    fi
done < "$INPUT_FILE"

if [ "${#all_lists[@]}" -eq 0 ]; then
    echo "Error: no valid WorldID lists found in file: $INPUT_FILE" >&2
    exit 1
fi

is_sid_selected() {
    local sid="$1"
    if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
        return 0
    fi
    for f in "${SET_FILTERS[@]}"; do
        if [ "$f" = "$sid" ]; then
            return 0
        fi
    done
    return 1
}

if [ "${#SET_FILTERS[@]}" -gt 0 ]; then
    declare -a all_lists_full=( "${all_lists[@]}" )
    all_lists=()

    unset set_lists
    unset set_first_list
    declare -A set_lists
    declare -A set_first_list

    for lid in "${all_lists_full[@]}"; do
        sid="${list_setid[$lid]}"
        if is_sid_selected "$sid"; then
            all_lists+=( "$lid" )
            if [[ -z "${set_lists[$sid]+x}" ]]; then
                set_lists["$sid"]="$lid"
                set_first_list["$sid"]="$lid"
            else
                set_lists["$sid"]+=" $lid"
            fi
        fi
    done

    if [ "${#all_lists[@]}" -eq 0 ]; then
        echo "Error: SetID filters matched no lists in file: $INPUT_FILE" >&2
        echo "  Filters: ${SET_FILTERS[*]}" >&2
        exit 1
    fi
fi

echo "Input file      : $INPUT_FILE"
echo "Environment     : $ENVIRONMENT"
echo "FILE_NAME       : $FILE_NAME"
if [ "${#SET_FILTERS[@]}" -gt 0 ]; then
    echo "SetID filters   : ${SET_FILTERS[*]}"
else
    echo "SetID filters   : (all)"
fi
echo "removebackup scope: $REMOVE_SCOPE"
echo "Lists by SET_ID :"
for set_id in "${!set_lists[@]}"; do
    echo "  SET_ID $set_id => ${set_lists[$set_id]}"
done
echo

sanitize_name() {
    local s="$1"
    s="${s//[^A-Za-z0-9._-]/_}"
    printf '%s' "$s"
}

make_run_log_dir() {
    local env="$1"
    local fname="$2"

    local env_s fname_s
    env_s="$(sanitize_name "$env")"
    fname_s="$(sanitize_name "$fname")"

    local base="/tmp/merge_${env_s}_${fname_s}"

    local seq
    for seq in $(seq -w 1 999); do
        local dir="${base}_${seq}"
        if mkdir -p "$dir" 2>/dev/null; then
            printf '%s\n' "$dir"
            return 0
        fi
    done

    echo "Error: cannot create log dir under /tmp (base=${base})" >&2
    return 1
}

LOG_DIR="$(make_run_log_dir "$ENVIRONMENT" "$FILE_NAME")"
echo "Log dir         : $LOG_DIR"
echo

run_step() {
    local step_no="$1"
    local step_name="$2"
    shift 2

    local logfile="${LOG_DIR}/${step_no}_${step_name}.log"
    local fifo
    fifo="$(mktemp -u "${LOG_DIR}/.fifo.${step_no}.${step_name}.XXXXXX")"
    mkfifo "$fifo"

    {
        echo "============================================================"
        echo "STEP ${step_no} : ${step_name}"
        echo "Time : $(date '+%Y-%m-%d %H:%M:%S')"
        echo "ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}, MODULE=${MODULE}"
        echo "SET_FILTERS=${SET_FILTERS[*]:-(all)}"
        echo "REMOVE_SCOPE=${REMOVE_SCOPE}"
        echo "============================================================"
    } >>"$logfile"

    tee -a "$logfile" <"$fifo" &
    local tee_pid=$!

    local rc=0
    set +e
    "$@" >"$fifo" 2>&1
    rc=$?
    set -e

    rm -f "$fifo" || true
    wait "$tee_pid" || true

    return "$rc"
}

FIRST_LID_GLOBAL="${all_lists[0]}"
FIRST_SETID_GLOBAL="${list_setid[$FIRST_LID_GLOBAL]}"

get_area_machine_and_generator() {
    local env="$1"
    local sid="$2"
    local area=""
    local machine=""
    local generator=""

    if (( sid >= 10 && sid < 20 )); then
        area="asia"
    elif (( sid >= 20 && sid < 30 )); then
        area="us"
    elif (( sid >= 30 && sid < 40 )); then
        area="eu"
    else
        echo "Error: unsupported SET_ID '$sid' (only 10~39 supported)." >&2
        return 1
    fi

    if [ "$env" = "live" ]; then
        machine="MS${sid}"
        generator="generate_merge_preparation"
    else
        case "$area" in
            asia) machine="MERGE_ASIA" ;;
            us)   machine="MERGE_US" ;;
            eu)   machine="MERGE_EU" ;;
        esac
        generator="generate_merge_preparation_test"
    fi

    printf '%s %s %s\n' "$area" "$machine" "$generator"
}

WAIT_INTERVAL=60

run_generate() {
    echo ">>> MODULE: generate (echo create merge directories and files)"
    for set_id in "${!set_lists[@]}"; do
        read AREA MACHINE GENERATOR < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        read -r -a LIDS <<< "${set_lists[$set_id]}"

        echo "==== [GENERATE] SET_ID ${set_id} / AREA=${AREA} / MACHINE=${MACHINE} / GEN=${GENERATOR} ===="
        for lid in "${LIDS[@]}"; do
            WORLD_ID_LISTS="${list_worlds[$lid]}"
            echo "# [${GENERATOR}] $MACHINE $DIRECTORY_NAME :: $WORLD_ID_LISTS"
            echo "ssh \"$MACHINE\" \"\$HOME/bin/${GENERATOR} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}\""
        done
    done
    echo
}

run_reward_before_merge() {
    echo ">>> MODULE: reward_before_merge (echo send_fastcgi_all_once_n1_by_world_reward_merge)"
    for lid in "${all_lists[@]}"; do
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        echo "# [reward_before_merge] LID=${lid} :: ${WORLD_ID_LISTS}"
        echo "\$HOME/bin/send_fastcgi_all_once_n1_by_world_reward_merge ${ENVIRONMENT} ${WORLD_ID_LISTS}"
    done
    echo
}

run_db_connect_verify() {
    echo ">>> MODULE: dbcheck (echo merge_db_connect_verify)"
    echo "==== [DB CONNECT VERIFY] ===="
    for set_id in "${!set_lists[@]}"; do
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        read -r -a LIDS <<< "${set_lists[$set_id]}"

        for lid in "${LIDS[@]}"; do
            WORLD_ID_LISTS="${list_worlds[$lid]}"
            WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
            REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
            REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

            echo "# [merge_db_connect_verify] $MACHINE $REMOTE_MERGE_INI"
            echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_connect_verify \\\"${REMOTE_MERGE_INI}\\\"\""
        done
    done
    echo
}

run_pre_backup() {
    echo ">>> MODULE: prebackup (echo Pre DB backup: AccountDB / GameDB / WorldDB)"

    read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$FIRST_SETID_GLOBAL")
    DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
    WORLD_ID_LISTS_FIRST_GLOBAL="${list_worlds[$FIRST_LID_GLOBAL]}"
    WORLD_ID_LISTS_STR_FIRST_GLOBAL=${WORLD_ID_LISTS_FIRST_GLOBAL// /_}
    REMOTE_BASE_DIR_FIRST_GLOBAL="\$HOME/servers${FIRST_SETID_GLOBAL}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_GLOBAL}"
    REMOTE_MERGE_INI_FIRST_GLOBAL="${REMOTE_BASE_DIR_FIRST_GLOBAL}/merge_setup.ini"

    echo "==== [PRE BACKUP] Global First List (AccountDB) ===="
    echo "# [Pre-AccountDB] $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
    echo "# DANGEROUS: DB backup operation"
    echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_backup \\\"${REMOTE_MERGE_INI_FIRST_GLOBAL}\\\" _${FILE_NAME}_Pre AccountDB\""
    echo

    echo "==== [PRE BACKUP] Per SET_ID First List (GameDB) ===="
    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS_FIRST_SET="${list_worlds[$FIRST_LID]}"
        WORLD_ID_LISTS_STR_FIRST_SET=${WORLD_ID_LISTS_FIRST_SET// /_}
        REMOTE_BASE_DIR_FIRST_SET="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_SET}"
        REMOTE_MERGE_INI_FIRST_SET="${REMOTE_BASE_DIR_FIRST_SET}/merge_setup.ini"

        echo "# [Pre-GameDB] SET_ID=${set_id} first list: $FIRST_LID ($WORLD_ID_LISTS_FIRST_SET) on $MACHINE"
        echo "# DANGEROUS: DB backup operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_backup \\\"${REMOTE_MERGE_INI_FIRST_SET}\\\" _${FILE_NAME}_Pre GameDB\""
    done
    echo

    echo "==== [PRE BACKUP] Each List (WorldDB) ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [Pre-WorldDB] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "# DANGEROUS: DB backup operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_backup \\\"${REMOTE_MERGE_INI}\\\" _${FILE_NAME}_Pre WorldDB\""
    done
    echo
}

run_restore_prebackup() {
    echo ">>> MODULE: restore_prebackup (echo restore DB from Pre backup: AccountDB / GameDB / WorldDB)"

    read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$FIRST_SETID_GLOBAL")
    DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
    WORLD_ID_LISTS_FIRST_GLOBAL="${list_worlds[$FIRST_LID_GLOBAL]}"
    WORLD_ID_LISTS_STR_FIRST_GLOBAL=${WORLD_ID_LISTS_FIRST_GLOBAL// /_}
    REMOTE_BASE_DIR_FIRST_GLOBAL="\$HOME/servers${FIRST_SETID_GLOBAL}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_GLOBAL}"
    REMOTE_MERGE_INI_FIRST_GLOBAL="${REMOTE_BASE_DIR_FIRST_GLOBAL}/merge_setup.ini"

    echo "==== [RESTORE PRE] Global First List (AccountDB) ===="
    echo "# [Restore-AccountDB] $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
    echo "# DANGEROUS: DB restore operation"
    echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_restore \\\"${REMOTE_MERGE_INI_FIRST_GLOBAL}\\\" _${FILE_NAME}_Pre AccountDB\""
    echo

    echo "==== [RESTORE PRE] Per SET_ID First List (GameDB) ===="
    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS_FIRST_SET="${list_worlds[$FIRST_LID]}"
        WORLD_ID_LISTS_STR_FIRST_SET=${WORLD_ID_LISTS_FIRST_SET// /_}
        REMOTE_BASE_DIR_FIRST_SET="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_SET}"
        REMOTE_MERGE_INI_FIRST_SET="${REMOTE_BASE_DIR_FIRST_SET}/merge_setup.ini"

        echo "# [Restore-GameDB] SET_ID=${set_id} first list: $FIRST_LID ($WORLD_ID_LISTS_FIRST_SET) on $MACHINE"
        echo "# DANGEROUS: DB restore operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_restore \\\"${REMOTE_MERGE_INI_FIRST_SET}\\\" _${FILE_NAME}_Pre GameDB\""
    done
    echo

    echo "==== [RESTORE PRE] Each List (WorldDB) ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [Restore-WorldDB] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "# DANGEROUS: DB restore operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_restore \\\"${REMOTE_MERGE_INI}\\\" _${FILE_NAME}_Pre WorldDB\""
    done
    echo
}

run_merge_rollback_partial_worlds() {
    echo ">>> MODULE: merge_rollback_partial_worlds (echo rollback partial worlds)"
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [merge_rollback_partial_worlds] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "# DANGEROUS: rollback operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_rollback_partial_worlds \\\"${REMOTE_MERGE_INI}\\\" \\\"${FILE_NAME}_Pre\\\"\""
    done
    echo
}

run_merge_exec() {
    echo ">>> MODULE: merge (echo merge_exec)"
    echo "==== [MERGE EXEC] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"

        echo "# [merge_exec] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "# DANGEROUS: runs merge_exec on remote"
        echo "ssh \"$MACHINE\" \"cd ${REMOTE_BASE_DIR}; nohup ./merge_exec &\""
    done
    echo
}

run_log_check() {
    echo ">>> MODULE: logcheck (echo first grep on Test.log*)"
    echo "==== [LOG CHECK - FIRST GREP] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"

        echo "# [log check] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "ssh \"$MACHINE\" \"cd ${REMOTE_BASE_DIR}; grep -E ',WolrdDB|,WorldDB|,GameDB|,AccountDB|,Done| autosend_info| redenvelope_info' Test.log* || echo 'No match in Test.log*'\""
    done
    echo
}

run_wait_done() {
    echo ">>> MODULE: waitdone (echo wait commands for ',Done')"
    echo "==== [WAIT ALL LISTS DONE - SAMPLE GREP COMMANDS] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"

        echo "# [wait-check] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "ssh \"$MACHINE\" \"cd ${REMOTE_BASE_DIR}; grep -q ',Done' Test.log* 2>/dev/null\""
    done
    echo
}

run_update_guild_race_data() {
    echo ">>> MODULE: update_guild_race_data (echo merge_update_guild_race_data with merge_setup.ini path)"
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [merge_update_guild_race_data] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE : $REMOTE_MERGE_INI"
        echo "# DANGEROUS: modifies DB data"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_update_guild_race_data \\\"${REMOTE_MERGE_INI}\\\"\""
    done
    echo
}

run_update_real_world_id_for_source_wids_on_fake_worlds() {
    echo ">>> MODULE: update_real_world_id_for_source_wids_on_fake_worlds (echo merge_update_real_world_id_for_source_wids_on_fake_worlds with merge_setup.ini path)"
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [merge_update_real_world_id_for_source_wids_on_fake_worlds] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE : $REMOTE_MERGE_INI"
        echo "# DANGEROUS: modifies DB data"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_update_real_world_id_for_source_wids_on_fake_worlds \\\"${REMOTE_MERGE_INI}\\\"\""
    done
    echo
}

run_post_backup() {
    echo ">>> MODULE: postbackup (echo Post DB backup)"

    echo "==== [POST BACKUP] Global First List (AccountDB) ===="
    read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$FIRST_SETID_GLOBAL")
    DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
    WORLD_ID_LISTS_FIRST_GLOBAL="${list_worlds[$FIRST_LID_GLOBAL]}"
    WORLD_ID_LISTS_STR_FIRST_GLOBAL=${WORLD_ID_LISTS_FIRST_GLOBAL// /_}
    REMOTE_BASE_DIR_FIRST_GLOBAL="\$HOME/servers${FIRST_SETID_GLOBAL}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_GLOBAL}"
    REMOTE_MERGE_INI_FIRST_GLOBAL="${REMOTE_BASE_DIR_FIRST_GLOBAL}/merge_setup.ini"

    echo "# [Post-AccountDB] First global list: $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
    echo "# DANGEROUS: DB backup operation"
    echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_backup \\\"${REMOTE_MERGE_INI_FIRST_GLOBAL}\\\" _${FILE_NAME}_Post AccountDB\""
    echo

    echo "==== [POST BACKUP] Per SET_ID First List (GameDB) ===="
    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS_FIRST_SET="${list_worlds[$FIRST_LID]}"
        WORLD_ID_LISTS_STR_FIRST_SET=${WORLD_ID_LISTS_FIRST_SET// /_}
        REMOTE_BASE_DIR_FIRST_SET="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_SET}"
        REMOTE_MERGE_INI_FIRST_SET="${REMOTE_BASE_DIR_FIRST_SET}/merge_setup.ini"

        echo "# [Post-GameDB] SET_ID=${set_id} first list: $FIRST_LID ($WORLD_ID_LISTS_FIRST_SET) on $MACHINE"
        echo "# DANGEROUS: DB backup operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_backup \\\"${REMOTE_MERGE_INI_FIRST_SET}\\\" _${FILE_NAME}_Post GameDB\""
    done
    echo

    echo "==== [POST BACKUP] Each List (WorldDB) ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [Post-WorldDB] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo "# DANGEROUS: DB backup operation"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_db_backup \\\"${REMOTE_MERGE_INI}\\\" _${FILE_NAME}_Post WorldDB\""
    done
    echo
}

run_check_diff() {
    echo ">>> MODULE: check (echo merge_check, with ENVIRONMENT)"
    echo "==== [CHECK PRE vs POST] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [merge_check] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE (ENV=${ENVIRONMENT})"
        echo "# DANGEROUS: reads and compares DB data"
        echo "ssh \"$MACHINE\" \"\$HOME/bin/merge_check \\\"${REMOTE_MERGE_INI}\\\" _${FILE_NAME}_Pre _${FILE_NAME}_Post ${ENVIRONMENT}\""
    done
    echo
}

run_update_db_setup() {
    echo ">>> MODULE: update_db_setup (echo update_db_setup_for_merge, only ENV=live)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# update_db_setup_for_merge is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

    for lid in "${all_lists[@]}"; do
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        echo "# [update_db_setup_for_merge] LID=${lid} :: ${WORLD_ID_LISTS}"
        echo "# DANGEROUS: modifies db_setup"
        echo "\$HOME/bin/update_db_setup_for_merge ${WORLD_ID_LISTS}"
    done
    echo
}

run_move_old_server_directory() {
    echo ">>> MODULE: move_old_server_directory (echo move_source_wids_directory_after_merge locally)"

    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA _ _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"

        echo "# [move_source_wids_directory_after_merge] LID=${lid}, SET_ID=${set_id}, ENV=${ENVIRONMENT}, DIR=${DIRECTORY_NAME} :: ${WORLD_ID_LISTS}"
        echo "# DANGEROUS: moves/removes server directories"
        echo "\$HOME/bin/move_source_wids_directory_after_merge ${ENVIRONMENT} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}"
    done
    echo
}

run_remove_source_wids_on_hosts() {
    echo ">>> MODULE: remove_source_wids_on_hosts (echo backup hosts and call remove_source_wids_on_hosts_after_merge, ENV=live only)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# remove_source_wids_on_hosts is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

    HOSTS_FILE="\$HOME/bin/hosts"
    HOSTS_BAK="\$HOME/bin/hosts.new"

    echo "==== [remove_source_wids_on_hosts] ENV=live ===="
    echo "# DANGEROUS: modifies hosts.new (and later will apply to hosts)"
    echo "# Backup hosts:"
    echo "cp -arf ${HOSTS_FILE} ${HOSTS_BAK}"

    echo "# Call remove_source_wids_on_hosts_after_merge for each WORLD_ID_LISTS:"
    for lid in "${all_lists[@]}"; do
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        echo "# [remove_source_wids_on_hosts_after_merge] LID=${lid} :: ${WORLD_ID_LISTS}"
        echo "\$HOME/bin/remove_source_wids_on_hosts_after_merge ${WORLD_ID_LISTS}"
    done
    echo
}

run_apply_hosts_new_and_sync2live_on_test() {
    echo ">>> MODULE: apply_hosts_new_and_sync2live_on_test (echo apply hosts.new to TEST and sync to live, only ENV=live)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# apply_hosts_new_and_sync2live_on_test is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

    echo "# DANGEROUS: applies hosts.new to TEST and sync to live"
    echo "\$HOME/bin/merge_apply_hosts_new_and_sync2live_on_test"
    echo
}

run_check_dbsetup_modification() {
    echo ">>> MODULE: check_dbsetup_modification (echo check db_setup modification, only ENV=live)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# check_dbsetup_modification is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

    echo "\$HOME/bin/check_dbsetup_modification_after_merge ${FILE_NAME}"
    echo
}

run_check_source_wids_directory() {
    echo ">>> MODULE: check_source_wids_directory (echo check source wids directory removal after merge)"
    echo "\$HOME/bin/check_source_wids_directory_after_merge ${ENVIRONMENT} ${FILE_NAME}"
    echo
}

run_check_source_wids_on_hosts() {
    echo ">>> MODULE: check_source_wids_on_hosts (echo check hosts removal, only ENV=live)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# check_source_wids_on_hosts is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

    echo "\$HOME/bin/check_source_wids_on_hosts_after_merge ${FILE_NAME}"
    echo
}

run_check_worlds_related_modification() {
    echo ">>> MODULE: check_worlds_related_modification (echo check worlds & fake_worlds modification after merge)"
    echo "\$HOME/bin/check_worlds_related_modification_after_merge ${ENVIRONMENT} ${FILE_NAME}"
    echo
}

run_after_merge_live() {
    echo ">>> GROUP: after_merge_live"
    run_step 01 update_db_setup             run_update_db_setup
    run_step 02 move_old_server_directory   run_move_old_server_directory
    run_step 03 remove_source_wids_on_hosts run_remove_source_wids_on_hosts
    run_step 04 apply_hosts_new_and_sync2live_on_test run_apply_hosts_new_and_sync2live_on_test
}
run_after_merge_test() {
    echo ">>> GROUP: after_merge_test"
    run_step 01 move_old_server_directory   run_move_old_server_directory
}
run_after_merge() {
    echo ">>> GROUP: after_merge"
    if [ "$ENVIRONMENT" = "live" ]; then
        run_after_merge_live
    else
        run_after_merge_test
    fi
}

run_after_merge_verify_live() {
    echo ">>> GROUP: after_merge_verify_live"
    run_step 01 check_dbsetup_modification        run_check_dbsetup_modification
    run_step 02 check_source_wids_directory       run_check_source_wids_directory
    run_step 03 check_source_wids_on_hosts        run_check_source_wids_on_hosts
    run_step 04 check_worlds_related_modification run_check_worlds_related_modification
}
run_after_merge_verify_test() {
    echo ">>> GROUP: after_merge_verify_test"
    run_step 01 check_source_wids_directory       run_check_source_wids_directory
    run_step 02 check_worlds_related_modification run_check_worlds_related_modification
}
run_after_merge_verify() {
    echo ">>> GROUP: after_merge_verify"
    if [ "$ENVIRONMENT" = "live" ]; then
        run_after_merge_verify_live
    else
        run_after_merge_verify_test
    fi
}

run_remove_backup_core() {
    local scope="$1"
    local backup_flag="${2-}"

    declare -a patterns=()
    case "$scope" in
        pre)  patterns=( "_${FILE_NAME}_Pre" ) ;;
        post) patterns=( "_${FILE_NAME}_Post" ) ;;
        both) patterns=( "_${FILE_NAME}_Pre" "_${FILE_NAME}_Post" ) ;;
        *)
            echo "Error: removebackup scope must be pre|post|both, got '$scope'." >&2
            exit 1
            ;;
    esac

    echo ">>> MODULE: removebackup (echo drop backup databases scope=${scope}, backup_flag=${backup_flag:-default})"

    if [ "$ENVIRONMENT" = "test" ]; then
        echo "==== [removebackup] ENV=test ===="
        echo "# (echo only) test env will ssh to MERGE_* machines"
        echo "# DANGEROUS: drops databases by pattern"
        for p in "${patterns[@]}"; do
            echo "# pattern: ${p}"
        done
        echo
        return
    fi

    if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
        echo "# [removebackup] live env via sendscript alldb for FILE_NAME=${FILE_NAME}"
        for p in "${patterns[@]}"; do
            echo "# DANGEROUS: drops databases by pattern"
            if [ -n "$backup_flag" ]; then
                echo "sendscript alldb <<< \"\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p} ${backup_flag}\""
            else
                echo "sendscript alldb <<< \"\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p}\""
            fi
        done
    else
        for sid in "${SET_FILTERS[@]}"; do
            m="set${sid}"
            echo "# [removebackup] live env on ${m} for FILE_NAME=${FILE_NAME}"
            for p in "${patterns[@]}"; do
                echo "# DANGEROUS: drops databases by pattern"
                if [ -n "$backup_flag" ]; then
                    echo "sendscript $m <<< \"\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p} ${backup_flag}\""
                else
                    echo "sendscript $m <<< \"\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p}\""
                fi
            done
        done
    fi
    echo
}

run_remove_backup()      { run_remove_backup_core "$REMOVE_SCOPE" ""; }
run_remove_backup_wb()   { run_remove_backup_core "$REMOVE_SCOPE" "1"; }
run_remove_backup_wob()  { run_remove_backup_core "$REMOVE_SCOPE" "0"; }

case "$MODULE" in
    exec_merge)
        echo ">>> GROUP: exec_merge"
        run_step 01 generate               run_generate
        run_step 02 dbcheck                run_db_connect_verify
        run_step 03 prebackup              run_pre_backup
        run_step 04 merge                  run_merge_exec
        run_step 05 logcheck               run_log_check
        run_step 06 waitdone               run_wait_done
        run_step 07 update_guild_race_data run_update_guild_race_data
        run_step 08 update_real_world_id_for_source_wids_on_fake_worlds run_update_real_world_id_for_source_wids_on_fake_worlds
        run_step 09 postbackup             run_post_backup
        run_step 10 check                  run_check_diff
        ;;
    before_merge)
        echo ">>> GROUP: before_merge"
        run_step 01 generate             run_generate
        run_step 02 reward_before_merge  run_reward_before_merge
        ;;
    generate) run_step 01 generate run_generate ;;
    reward_before_merge) run_step 01 reward_before_merge run_reward_before_merge ;;
    dbcheck) run_step 01 dbcheck run_db_connect_verify ;;
    prebackup) run_step 01 prebackup run_pre_backup ;;
    restore_prebackup) run_step 01 restore_prebackup run_restore_prebackup ;;
    merge_rollback_partial_worlds) run_step 01 merge_rollback_partial_worlds run_merge_rollback_partial_worlds ;;
    merge) run_step 01 merge run_merge_exec ;;
    logcheck) run_step 01 logcheck run_log_check ;;
    waitdone) run_step 01 waitdone run_wait_done ;;
    update_guild_race_data) run_step 01 update_guild_race_data run_update_guild_race_data ;;
    update_real_world_id_for_source_wids_on_fake_worlds) run_step 01 update_real_world_id_for_source_wids_on_fake_worlds run_update_real_world_id_for_source_wids_on_fake_worlds ;;
    postbackup) run_step 01 postbackup run_post_backup ;;
    check) run_step 01 check run_check_diff ;;
    update_db_setup) run_step 01 update_db_setup run_update_db_setup ;;
    move_old_server_directory) run_step 01 move_old_server_directory run_move_old_server_directory ;;
    remove_source_wids_on_hosts) run_step 01 remove_source_wids_on_hosts run_remove_source_wids_on_hosts ;;
    apply_hosts_new_and_sync2live_on_test) run_step 01 apply_hosts_new_and_sync2live_on_test run_apply_hosts_new_and_sync2live_on_test ;;
    check_dbsetup_modification) run_step 01 check_dbsetup_modification run_check_dbsetup_modification ;;
    check_source_wids_directory) run_step 01 check_source_wids_directory run_check_source_wids_directory ;;
    check_source_wids_on_hosts) run_step 01 check_source_wids_on_hosts run_check_source_wids_on_hosts ;;
    check_worlds_related_modification) run_step 01 check_worlds_related_modification run_check_worlds_related_modification ;;
    after_merge) run_step 01 after_merge run_after_merge ;;
    after_merge_live) run_step 01 after_merge_live run_after_merge_live ;;
    after_merge_test) run_step 01 after_merge_test run_after_merge_test ;;
    after_merge_verify) run_step 01 after_merge_verify run_after_merge_verify ;;
    after_merge_verify_live) run_step 01 after_merge_verify_live run_after_merge_verify_live ;;
    after_merge_verify_test) run_step 01 after_merge_verify_test run_after_merge_verify_test ;;
    removebackup) run_step 01 removebackup run_remove_backup ;;
    removebackup_wb) run_step 01 removebackup_wb run_remove_backup_wb ;;
    removebackup_wob) run_step 01 removebackup_wob run_remove_backup_wob ;;
    end_merge)
        echo ">>> GROUP: end_merge"
        run_step 01 end_merge run_remove_backup
        ;;
    re_run_exec_merge)
        echo ">>> GROUP: re_run_exec_merge"
        echo "# re_run_exec_merge is supported in echo script; commands will be printed by each module."
        _saved_scope="$REMOVE_SCOPE"
        REMOVE_SCOPE="post"
        run_step 01 removebackup_wob_post         run_remove_backup_wob
        REMOVE_SCOPE="both"
        run_step 02 restore_prebackup             run_restore_prebackup
        run_step 03 merge_rollback_partial_worlds run_merge_rollback_partial_worlds
        run_step 04 merge                         run_merge_exec
        run_step 05 logcheck                      run_log_check
        run_step 06 waitdone                      run_wait_done
        run_step 07 update_guild_race_data        run_update_guild_race_data
        run_step 08 update_real_world_id_for_source_wids_on_fake_worlds run_update_real_world_id_for_source_wids_on_fake_worlds
        run_step 09 postbackup                    run_post_backup
        run_step 10 check                         run_check_diff
        REMOVE_SCOPE="$_saved_scope"
        ;;
    re_run_pre_merge)
        echo ">>> GROUP: re_run_pre_merge"
        echo "# re_run_pre_merge is supported in echo script; commands will be printed by each module."
        _saved_scope="$REMOVE_SCOPE"
        REMOVE_SCOPE="post"
        run_step 01 removebackup_wob_post         run_remove_backup_wob
        REMOVE_SCOPE="both"
        run_step 02 restore_prebackup             run_restore_prebackup
        run_step 03 merge_rollback_partial_worlds run_merge_rollback_partial_worlds
        REMOVE_SCOPE="$_saved_scope"
        ;;
    re_run_exec_merge_wo_restore)
        echo ">>> GROUP: re_run_exec_merge_wo_restore"
        echo "# re_run_exec_merge_wo_restore is supported in echo script; commands will be printed by each module."
        run_step 01 merge                  run_merge_exec
        run_step 02 logcheck               run_log_check
        run_step 03 waitdone               run_wait_done
        run_step 04 update_guild_race_data run_update_guild_race_data
        run_step 05 update_real_world_id_for_source_wids_on_fake_worlds run_update_real_world_id_for_source_wids_on_fake_worlds
        run_step 06 postbackup             run_post_backup
        run_step 07 check                  run_check_diff
        ;;
    *)
        echo "Error: unknown MODULE '${MODULE}'." >&2
        print_usage_and_exit
        ;;
esac

END_TS=$(date +%s)
END_TIME=$(date '+%Y-%m-%d %H:%M:%S')
ELAPSED=$((END_TS - START_TS))
ELAPSED_H=$((ELAPSED / 3600))
ELAPSED_M=$(((ELAPSED % 3600) / 60))
ELAPSED_S=$((ELAPSED % 60))

echo "== run_merge_n1_echo finished (MODULE=${MODULE}) =="
echo "Start time : ${START_TIME}"
echo "End time   : ${END_TIME}"
printf 'Elapsed    : %02d 小時 %02d 分 %02d 秒\n' "$ELAPSED_H" "$ELAPSED_M" "$ELAPSED_S"
echo "ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}"
echo "Log dir    : ${LOG_DIR}"
echo "Dry-run (echo) 完成，以上為所有預計執行的指令。"

