#!/usr/bin/env bash

set -euo pipefail

SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$SCRIPT_DIR/../cmake_bash_library/cmake_bash_args.bash" ]]; then
    # shellcheck source=../cmake_bash_library/cmake_bash_args.bash
    source "$SCRIPT_DIR/../cmake_bash_library/cmake_bash_args.bash"
fi

AVAILABLE_STEPS=(
    prepare
    export_compdb
    clean
    build
    stats
    top
    trace
    graphviz
)

DEFAULT_PIPELINE_STEPS=(
    prepare
    export_compdb
    build
    stats
    top
    trace
    graphviz
)

CLEAN_PIPELINE_STEPS=(
    prepare
    export_compdb
    clean
    build
    stats
    top
    trace
    graphviz
)

PARAM_FILE=""
SOURCE_DIR=""
BUILD_DIR=""
TARGET="all"
JOBS=""
OUT_DIR=""
TOP_N="30"
CMAKE_EXE="cmake"
NINJA_EXE="ninja"
DOT_EXE=""
CLEAN_FIRST="OFF"
ACTION=""
STEP=""
STEPS=""
FORMAT="text"

usage() {
    cat <<USAGE_EOF
用法：
  $SCRIPT_NAME steps [--format text|plain|json]
  $SCRIPT_NAME pipeline [--clean-first ON|OFF] [--format text|plain|json]
  $SCRIPT_NAME run --step <step> [配置参数]
  $SCRIPT_NAME run --steps <step1,step2,...> [配置参数]
  $SCRIPT_NAME run-all [--clean-first ON|OFF] [配置参数]
  $SCRIPT_NAME clear --out-dir <dir>

可执行步骤：
  prepare        检查输入目录和 Ninja 构建目录，初始化输出摘要
  export_compdb  导出 compile_commands.json
  clean          执行 CMake clean target
  build          构建目标并记录构建耗时
  stats          导出 ninja -t stats
  top            统计最慢编译和链接边
  trace          生成 Chrome Trace JSON
  graphviz       生成 CMake target 依赖图

配置参数：
  --source-dir <dir>     源码目录
  --build-dir <dir>      Ninja 构建目录
  --target <name>        构建目标，默认 all
  --jobs <n>             并行数量
  --out-dir <dir>        输出目录
  --top-n <n>            top 统计数量，默认 30
  --cmake-exe <path>     cmake 可执行文件，默认 cmake
  --ninja-exe <path>     ninja 可执行文件，默认 ninja
  --dot-exe <path>       dot 可执行文件，可选
  --clean-first ON|OFF   run-all 是否先 clean，默认 OFF

示例：
  $SCRIPT_NAME steps
  $SCRIPT_NAME pipeline --clean-first ON
  $SCRIPT_NAME run --step top --source-dir . --build-dir build --out-dir build/build_analysis
  $SCRIPT_NAME run-all --clean-first ON --source-dir . --build-dir build --target all --jobs 8 --out-dir build/build_analysis
USAGE_EOF
}

print_common_usage() {
    usage
    echo
    echo "统一入口："
    echo "  $SCRIPT_NAME help"
    echo "  $SCRIPT_NAME list_actions"
    echo "  $SCRIPT_NAME --param-file <file> <action>"
}

step_desc() {
    case "$1" in
        prepare) echo "检查输入目录和 Ninja 构建目录，初始化输出摘要" ;;
        export_compdb) echo "导出 compile_commands.json" ;;
        clean) echo "执行 CMake clean target" ;;
        build) echo "构建目标并记录构建耗时" ;;
        stats) echo "导出 ninja -t stats" ;;
        top) echo "统计最慢编译和链接边" ;;
        trace) echo "生成 Chrome Trace JSON" ;;
        graphviz) echo "生成 CMake target 依赖图" ;;
        *) echo "未知步骤" ;;
    esac
}

is_known_step() {
    local item
    for item in "${AVAILABLE_STEPS[@]}"; do
        if [[ "$item" == "$1" ]]; then
            return 0
        fi
    done
    return 1
}

normalize_on_off() {
    local value="$1"
    local lower
    lower="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')"
    case "$lower" in
        on|true|yes|1) echo "ON" ;;
        off|false|no|0) echo "OFF" ;;
        *)
            echo "[错误] 只能使用 ON 或 OFF：$value" >&2
            exit 1
            ;;
    esac
}

json_escape() {
    local s="$1"
    s="${s//\\/\\\\}"
    s="${s//\"/\\\"}"
    s="${s//$'\n'/\\n}"
    printf '%s' "$s"
}

print_steps() {
    local step
    case "$FORMAT" in
        plain)
            printf '%s\n' "${AVAILABLE_STEPS[@]}"
            ;;
        json)
            printf '['
            local first=1
            for step in "${AVAILABLE_STEPS[@]}"; do
                if [[ $first -eq 0 ]]; then
                    printf ','
                fi
                first=0
                printf '{"name":"%s","description":"%s"}' "$(json_escape "$step")" "$(json_escape "$(step_desc "$step")")"
            done
            printf ']\n'
            ;;
        text)
            echo "可执行步骤："
            for step in "${AVAILABLE_STEPS[@]}"; do
                printf '  %-13s %s\n' "$step" "$(step_desc "$step")"
            done
            ;;
        *)
            echo "[错误] 未知输出格式：$FORMAT" >&2
            exit 1
            ;;
    esac
}

print_actions() {
    printf '%s\n' list_actions pipeline run-all clear
    printf '%s\n' "${AVAILABLE_STEPS[@]}"
}

set_pipeline_array() {
    local clean_first_norm
    clean_first_norm="$(normalize_on_off "$CLEAN_FIRST")"
    if [[ "$clean_first_norm" == "ON" ]]; then
        _pipeline_steps=("${CLEAN_PIPELINE_STEPS[@]}")
    else
        _pipeline_steps=("${DEFAULT_PIPELINE_STEPS[@]}")
    fi
}

print_pipeline() {
    local clean_first_norm
    clean_first_norm="$(normalize_on_off "$CLEAN_FIRST")"

    set_pipeline_array

    case "$FORMAT" in
        plain)
            printf '%s\n' "${_pipeline_steps[@]}"
            ;;
        json)
            printf '{"clean_first":"%s","steps":[' "$clean_first_norm"
            local first=1
            local step
            for step in "${_pipeline_steps[@]}"; do
                if [[ $first -eq 0 ]]; then
                    printf ','
                fi
                first=0
                printf '"%s"' "$(json_escape "$step")"
            done
            printf ']}\n'
            ;;
        text)
            echo "默认执行顺序："
            local index=1
            local step
            for step in "${_pipeline_steps[@]}"; do
                printf '  %02d. %-13s %s\n' "$index" "$step" "$(step_desc "$step")"
                index=$((index + 1))
            done
            ;;
        *)
            echo "[错误] 未知输出格式：$FORMAT" >&2
            exit 1
            ;;
    esac
}

log_msg() {
    local text="$1"
    echo "$text"
    if [[ -n "${OUT_DIR:-}" ]]; then
        mkdir -p "$OUT_DIR"
        echo "$text" >> "$OUT_DIR/step.log"
    fi
}

require_value() {
    local name="$1"
    local value="$2"
    if [[ -z "$value" ]]; then
        echo "[错误] 缺少参数：$name" >&2
        exit 1
    fi
}

require_file() {
    local file="$1"
    local text="$2"
    if [[ ! -f "$file" ]]; then
        echo "[错误] $text：$file" >&2
        exit 1
    fi
}

require_dir() {
    local dir="$1"
    local text="$2"
    if [[ ! -d "$dir" ]]; then
        echo "[错误] $text：$dir" >&2
        exit 1
    fi
}

require_run_config() {
    require_value "--source-dir" "$SOURCE_DIR"
    require_value "--build-dir" "$BUILD_DIR"
    require_value "--out-dir" "$OUT_DIR"
    require_value "--cmake-exe" "$CMAKE_EXE"
    require_value "--ninja-exe" "$NINJA_EXE"
}

now_ms() {
    local value
    value="$(date +%s%3N 2>/dev/null || true)"
    if [[ "$value" =~ ^[0-9]+$ ]]; then
        echo "$value"
    else
        python3 - <<'PY_EOF'
import time
print(int(time.time() * 1000))
PY_EOF
    fi
}

write_step_metadata() {
    mkdir -p "$OUT_DIR"
    if [[ -n "$PARAM_FILE" ]]; then
        "$0" --param-file "$PARAM_FILE" steps --format text > "$OUT_DIR/available_steps.txt"
        "$0" --param-file "$PARAM_FILE" pipeline --format text > "$OUT_DIR/pipeline_steps.txt"
    else
        "$0" steps --format text > "$OUT_DIR/available_steps.txt"
        "$0" pipeline --clean-first "$CLEAN_FIRST" --format text > "$OUT_DIR/pipeline_steps.txt"
    fi
}

run_prepare() {
    require_dir "$SOURCE_DIR" "源码目录不存在"
    require_dir "$BUILD_DIR" "构建目录不存在"
    require_file "$BUILD_DIR/build.ninja" "当前构建目录不是 Ninja 构建目录，缺少 build.ninja"

    mkdir -p "$OUT_DIR"
    : > "$OUT_DIR/step.log"
    write_step_metadata

    log_msg "========== Ninja 构建分析：准备 =========="
    log_msg "源码目录：$SOURCE_DIR"
    log_msg "构建目录：$BUILD_DIR"
    log_msg "分析目标：${TARGET:-all}"
    log_msg "并行数量：${JOBS:-默认}"
    log_msg "输出目录：$OUT_DIR"
    log_msg "clean_first：$(normalize_on_off "$CLEAN_FIRST")"
    log_msg "========================================"

    cat > "$OUT_DIR/summary.txt" <<SUMMARY_EOF
源码目录：$SOURCE_DIR
构建目录：$BUILD_DIR
分析目标：${TARGET:-all}
并行数量：${JOBS:-默认}
输出目录：$OUT_DIR
clean_first：$(normalize_on_off "$CLEAN_FIRST")
SUMMARY_EOF
}

run_export_compdb() {
    mkdir -p "$OUT_DIR"
    require_file "$BUILD_DIR/build.ninja" "当前构建目录不是 Ninja 构建目录，缺少 build.ninja"
    log_msg "[步骤] 导出 compile_commands.json"
    "$NINJA_EXE" -C "$BUILD_DIR" -t compdb > "$OUT_DIR/compile_commands.json"
    log_msg "[完成] $OUT_DIR/compile_commands.json"
}

run_clean() {
    log_msg "[步骤] 执行 clean"
    "$CMAKE_EXE" --build "$BUILD_DIR" --target clean
    log_msg "[完成] clean"
}

run_build() {
    mkdir -p "$OUT_DIR"
    log_msg "[步骤] 删除旧 .ninja_log"
    rm -f "$BUILD_DIR/.ninja_log"

    log_msg "[步骤] 开始构建目标：${TARGET:-all}"
    local start_ms
    local end_ms
    local total_ms
    start_ms="$(now_ms)"

    if [[ -n "${TARGET:-}" && "$TARGET" != "all" ]]; then
        if [[ -n "${JOBS:-}" ]]; then
            "$CMAKE_EXE" --build "$BUILD_DIR" --target "$TARGET" --parallel "$JOBS" 2>&1 | tee "$OUT_DIR/build_stdout.log"
        else
            "$CMAKE_EXE" --build "$BUILD_DIR" --target "$TARGET" 2>&1 | tee "$OUT_DIR/build_stdout.log"
        fi
    else
        if [[ -n "${JOBS:-}" ]]; then
            "$CMAKE_EXE" --build "$BUILD_DIR" --parallel "$JOBS" 2>&1 | tee "$OUT_DIR/build_stdout.log"
        else
            "$CMAKE_EXE" --build "$BUILD_DIR" 2>&1 | tee "$OUT_DIR/build_stdout.log"
        fi
    fi

    end_ms="$(now_ms)"
    total_ms=$((end_ms - start_ms))

    log_msg "[完成] 构建总耗时：${total_ms} ms"
    echo "构建总耗时：${total_ms} ms" >> "$OUT_DIR/summary.txt"

    require_file "$BUILD_DIR/.ninja_log" "构建后没有生成 .ninja_log，可能没有实际执行编译或链接"
}

run_stats() {
    mkdir -p "$OUT_DIR"
    require_file "$BUILD_DIR/.ninja_log" "缺少 .ninja_log，无法导出统计"
    log_msg "[步骤] 导出 Ninja stats"
    "$NINJA_EXE" -C "$BUILD_DIR" -t stats > "$OUT_DIR/build_stats.txt" 2>&1 || true
    log_msg "[完成] $OUT_DIR/build_stats.txt"
}

run_top() {
    mkdir -p "$OUT_DIR"
    require_file "$BUILD_DIR/.ninja_log" "缺少 .ninja_log，无法排序耗时"

    log_msg "[步骤] 统计最慢构建边"

    awk -F '\t' '
        !/^#/ && NF >= 4 {
            dur = $2 - $1
            if (dur < 0) dur = 0
            printf "%10d ms\t%s\n", dur, $4
        }
    ' "$BUILD_DIR/.ninja_log" | sort -nr > "$OUT_DIR/top_all.txt"

    awk -F '\t' '
        !/^#/ && NF >= 4 && $4 ~ /\.(obj|o)$/ {
            dur = $2 - $1
            if (dur < 0) dur = 0
            printf "%10d ms\t%s\n", dur, $4
        }
    ' "$BUILD_DIR/.ninja_log" | sort -nr | head -n "$TOP_N" > "$OUT_DIR/top_compile.txt"

    awk -F '\t' '
        !/^#/ && NF >= 4 && $4 ~ /\.(exe|dll|lib|a|so|dylib)$/ {
            dur = $2 - $1
            if (dur < 0) dur = 0
            printf "%10d ms\t%s\n", dur, $4
        }
    ' "$BUILD_DIR/.ninja_log" | sort -nr | head -n "$TOP_N" > "$OUT_DIR/top_link.txt"

    log_msg "[完成] $OUT_DIR/top_compile.txt"
    log_msg "[完成] $OUT_DIR/top_link.txt"

    echo
    echo "========== 最慢编译 =========="
    cat "$OUT_DIR/top_compile.txt" || true

    echo
    echo "========== 最慢链接/归档 =========="
    cat "$OUT_DIR/top_link.txt" || true
}

run_trace() {
    mkdir -p "$OUT_DIR"
    require_file "$BUILD_DIR/.ninja_log" "缺少 .ninja_log，无法生成 trace"

    log_msg "[步骤] 生成 Chrome Trace"

    awk -F '\t' '
        function esc(s) {
            gsub(/\\/,"\\\\",s)
            gsub(/"/,"\\\"",s)
            return s
        }

        BEGIN {
            print "{"
            print "  \"traceEvents\": ["
            first = 1
        }

        !/^#/ && NF >= 4 {
            start = $1
            end = $2
            output = $4
            dur = end - start
            if (dur < 0) dur = 0

            name = output
            sub(/^.*[\\\/]/, "", name)

            if (!first) {
                print ","
            }
            first = 0

            printf "    {"
            printf "\"name\":\"%s\",", esc(name)
            printf "\"cat\":\"ninja\","
            printf "\"ph\":\"X\","
            printf "\"ts\":%d,", start * 1000
            printf "\"dur\":%d,", dur * 1000
            printf "\"pid\":1,"
            printf "\"tid\":1,"
            printf "\"args\":{\"output\":\"%s\",\"duration_ms\":%d}", esc(output), dur
            printf "}"
        }

        END {
            print ""
            print "  ],"
            print "  \"displayTimeUnit\": \"ms\""
            print "}"
        }
    ' "$BUILD_DIR/.ninja_log" > "$OUT_DIR/build.trace.json"

    log_msg "[完成] $OUT_DIR/build.trace.json"
    log_msg "[提示] 用 chrome://tracing 或 edge://tracing 导入 build.trace.json"
}

run_graphviz() {
    mkdir -p "$OUT_DIR"
    log_msg "[步骤] 生成 CMake target 依赖图"

    "$CMAKE_EXE" --graphviz="$OUT_DIR/dependency.dot" "$BUILD_DIR"
    log_msg "[完成] $OUT_DIR/dependency.dot"

    if [[ -n "${DOT_EXE:-}" ]] && command -v "$DOT_EXE" >/dev/null 2>&1; then
        "$DOT_EXE" -Tpng "$OUT_DIR/dependency.dot" -o "$OUT_DIR/dependency.png"
        log_msg "[完成] $OUT_DIR/dependency.png"
    else
        log_msg "[提示] 未找到 dot，跳过 png 生成，只保留 dependency.dot"
    fi
}

clear_outputs() {
    require_value "--out-dir" "$OUT_DIR"
    mkdir -p "$OUT_DIR"
    echo "清理 Ninja 构建分析生成文件：$OUT_DIR"
    rm -f \
        "$OUT_DIR/summary.txt" \
        "$OUT_DIR/compile_commands.json" \
        "$OUT_DIR/build_stats.txt" \
        "$OUT_DIR/top_all.txt" \
        "$OUT_DIR/top_compile.txt" \
        "$OUT_DIR/top_link.txt" \
        "$OUT_DIR/build.trace.json" \
        "$OUT_DIR/dependency.dot" \
        "$OUT_DIR/dependency.png" \
        "$OUT_DIR/build_stdout.log" \
        "$OUT_DIR/step.log" \
        "$OUT_DIR/available_steps.txt" \
        "$OUT_DIR/pipeline_steps.txt"
    echo "[完成] 清理完成"
}

run_step_by_name() {
    local step="$1"
    if ! is_known_step "$step"; then
        echo "[错误] 未知步骤：$step" >&2
        print_steps >&2
        exit 1
    fi

    require_run_config

    case "$step" in
        prepare) run_prepare ;;
        export_compdb) run_export_compdb ;;
        clean) run_clean ;;
        build) run_build ;;
        stats) run_stats ;;
        top) run_top ;;
        trace) run_trace ;;
        graphviz) run_graphviz ;;
    esac
}

run_steps_csv() {
    local csv="$1"
    local old_ifs="$IFS"
    local item
    IFS=','
    read -r -a _steps_from_csv <<< "$csv"
    IFS="$old_ifs"

    if [[ ${#_steps_from_csv[@]} -eq 0 ]]; then
        echo "[错误] --steps 不能为空" >&2
        exit 1
    fi

    for item in "${_steps_from_csv[@]}"; do
        item="${item//[[:space:]]/}"
        run_step_by_name "$item"
    done
}

run_pipeline() {
    local step
    set_pipeline_array
    for step in "${_pipeline_steps[@]}"; do
        run_step_by_name "$step"
    done
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                usage
                exit 0
                ;;
            --param-file)
                require_value "--param-file" "${2:-}"
                if ! declare -F cbl_load_param_file >/dev/null 2>&1; then
                    echo "[错误] cmake_bash_args.bash 未加载，无法使用 --param-file" >&2
                    exit 1
                fi
                PARAM_FILE="$2"
                cbl_load_param_file "$PARAM_FILE"
                shift 2
                ;;
            list_actions)
                ACTION="list_actions"
                shift
                ;;
            help)
                ACTION="help"
                shift
                ;;
            steps|list-steps|--list-steps)
                ACTION="steps"
                shift
                ;;
            pipeline|list-pipeline|--list-pipeline)
                ACTION="pipeline"
                shift
                ;;
            run)
                ACTION="run"
                shift
                ;;
            run-all|all|--run-all)
                ACTION="run_all"
                shift
                ;;
            clear|--clear)
                ACTION="clear"
                shift
                ;;
            --step)
                require_value "--step" "${2:-}"
                STEP="$2"
                if [[ -z "$ACTION" ]]; then
                    ACTION="run"
                fi
                shift 2
                ;;
            --steps)
                require_value "--steps" "${2:-}"
                STEPS="$2"
                if [[ -z "$ACTION" ]]; then
                    ACTION="run"
                fi
                shift 2
                ;;
            --source-dir)
                require_value "--source-dir" "${2:-}"
                SOURCE_DIR="$2"
                shift 2
                ;;
            --build-dir)
                require_value "--build-dir" "${2:-}"
                BUILD_DIR="$2"
                shift 2
                ;;
            --target)
                require_value "--target" "${2:-}"
                TARGET="$2"
                shift 2
                ;;
            --jobs)
                require_value "--jobs" "${2:-}"
                JOBS="$2"
                shift 2
                ;;
            --out-dir)
                require_value "--out-dir" "${2:-}"
                OUT_DIR="$2"
                shift 2
                ;;
            --top-n)
                require_value "--top-n" "${2:-}"
                TOP_N="$2"
                shift 2
                ;;
            --cmake-exe)
                require_value "--cmake-exe" "${2:-}"
                CMAKE_EXE="$2"
                shift 2
                ;;
            --ninja-exe)
                require_value "--ninja-exe" "${2:-}"
                NINJA_EXE="$2"
                shift 2
                ;;
            --dot-exe)
                require_value "--dot-exe" "${2:-}"
                DOT_EXE="$2"
                shift 2
                ;;
            --clean-first)
                require_value "--clean-first" "${2:-}"
                CLEAN_FIRST="$(normalize_on_off "$2")"
                shift 2
                ;;
            --format)
                require_value "--format" "${2:-}"
                FORMAT="$2"
                shift 2
                ;;
            *)
                if is_known_step "$1"; then
                    ACTION="run"
                    STEP="$1"
                    shift
                else
                    echo "[错误] 未知参数：$1" >&2
                    usage >&2
                    exit 1
                fi
                ;;
        esac
    done
}

main() {
    parse_args "$@"

    if [[ -z "$ACTION" ]]; then
        if [[ -n "$STEP" ]]; then
            ACTION="run"
        else
            usage
            exit 0
        fi
    fi

    case "$ACTION" in
        steps)
            print_steps
            ;;
        list_actions)
            print_actions
            ;;
        help)
            print_common_usage
            ;;
        pipeline)
            print_pipeline
            ;;
        run)
            if [[ -n "$STEPS" ]]; then
                run_steps_csv "$STEPS"
            else
                require_value "--step" "$STEP"
                run_step_by_name "$STEP"
            fi
            ;;
        run_all)
            run_pipeline
            ;;
        clear)
            clear_outputs
            ;;
        *)
            echo "[错误] 未知动作：$ACTION" >&2
            exit 1
            ;;
    esac
}

main "$@"
