#!/bin/bash
# Run IREE executables with Bazel.
#
# Usage: iree-bazel-run [options] <target> [bazel-args...] [-- program-args...]
#
# Examples:
#   iree-bazel-run //tools:iree-compile -- --help
#   iree-bazel-run //runtime/src/iree/tokenizer:tokenizer_test
#   iree-bazel-run --config=debug //tools:iree-opt -- input.mlir

set -e

# Source shared library.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/iree-bazel-lib"
iree_bazel_init "iree-bazel-run"

# Expand combined short flags (e.g., -nv -> -n -v).
eval "set -- $(iree_expand_combined_flags "$@")"

show_help() {
    cat << 'EOF'
iree-bazel-run - Run IREE executables with Bazel

USAGE
    iree-bazel-run [options] <target> [bazel-args...] [-- program-args...]

OPTIONS
    -n, --dry_run    Show the bazel command without executing
    -v, --verbose    Show the bazel command before executing
    -w, --watch      Watch mode: rebuild and restart on file changes
    -h, --help       Show this help
    --trace          Capture the run with Tracy; implies --config=perf --config=tracy
    --trace_name NAME, --trace-name NAME
                    Override the sanitized trace artifact name

    NOTE: Short flags can be combined: -nv is equivalent to -n -v

ARGUMENTS
    target         Bazel executable target (required)
    bazel-args     Arguments passed to bazel build (before --)
    program-args   Arguments passed to the executable (after --)

EXAMPLES
    iree-bazel-run //tools:iree-compile -- --help
    iree-bazel-run //tools:iree-opt -- input.mlir -o output.mlir
    iree-bazel-run //runtime/src/iree/tokenizer:tokenizer_test
    iree-bazel-run --config=debug //tools:iree-compile -- model.mlir
    iree-bazel-run -w //tools:iree-opt -- input.mlir  # Watch and restart on changes
    iree-bazel-run -nv //tools:iree-compile -- --help   # Show command only
    iree-bazel-run --trace //tools:iree-run-module -- --module=model.vmfb --device=local-task
    iree-bazel-run --trace --trace_name=run-module-sample //tools:iree-run-module -- --module=model.vmfb --device=local-task

WORKING DIRECTORY
    Unlike raw "bazel run", this tool runs the binary from your current
    working directory, so relative paths in arguments work as expected.

WATCH MODE
    Watch mode (-w) uses ibazel and holds the Bazel server lock while the
    binary runs. This is necessary for ibazel to control the process lifecycle
    and restart on changes. Normal mode releases the lock after building.

COMMON TARGETS
    //tools:iree-compile         Main compiler
    //tools:iree-opt             MLIR optimization tool
    //tools:iree-run-module      Execute compiled modules
    //tools:iree-benchmark-module  Performance benchmarking

CONFIGURATIONS
    --config=localdev        Local dev optimizations (disk cache, skymeld)
    --config=debug           No optimizations, assertions enabled
    --config=asserts         Optimized with assertions
    --config=asan            Address Sanitizer
    --config=msan            Memory Sanitizer
    --config=tsan            Thread Sanitizer
    --config=perf            Opt/NDEBUG/O3/thin-LTO/native-codegen run
    --config=tracy           Runtime Tracy instrumentation

TRACY CAPTURE
    --trace composes the normal build/run flow with a Tracy capture wrapper.
    The Bazel build shape comes from --config=perf and --config=tracy; the
    capture wrapper only starts tracy-capture and runs the already-built binary.
    Trace artifacts are written to .tracy/<timestamp>-<name>.tracy. By default
    the name is derived from the Bazel target; use --trace_name to override it.

SEE ALSO
    iree-bazel-build, iree-bazel-test, iree-bazel-query, iree-bazel-cquery
EOF
}

# Parse arguments.
WATCH_MODE=0
TRACE_MODE=0
TRACE_NAME=""
TARGET=""
BAZEL_ARGS=()
PROGRAM_ARGS=()
PARSING_PROGRAM_ARGS=0

while [[ $# -gt 0 ]]; do
    if [[ "${PARSING_PROGRAM_ARGS}" == "1" ]]; then
        PROGRAM_ARGS+=("${1}")
        shift
        continue
    fi

    case "${1}" in
        -h|--help)
            show_help
            exit 0
            ;;
        --agent-md|--agent_md)
            iree_show_agent_md
            exit 0
            ;;
        -n|--dry_run|--dry-run)
            IREE_BAZEL_DRY_RUN=1
            shift
            ;;
        -v|--verbose)
            IREE_BAZEL_VERBOSE=1
            shift
            ;;
        -w|--watch)
            WATCH_MODE=1
            shift
            ;;
        --trace)
            TRACE_MODE=1
            shift
            ;;
        --trace_name|--trace-name)
            if [[ $# -lt 2 ]] || [[ -z "${2}" ]] || [[ "${2}" == -* ]]; then
                iree_error "${1} requires a non-empty name"
                exit 1
            fi
            TRACE_NAME="${2}"
            shift 2
            ;;
        --trace_name=*|--trace-name=*)
            TRACE_NAME="${1#*=}"
            if [[ -z "${TRACE_NAME}" ]]; then
                iree_error "${1%%=*} requires a non-empty name"
                exit 1
            fi
            shift
            ;;
        --)
            PARSING_PROGRAM_ARGS=1
            shift
            ;;
        -*)
            BAZEL_ARGS+=("${1}")
            shift
            ;;
        *)
            if [[ -z "${TARGET}" ]]; then
                TARGET="${1}"
            else
                BAZEL_ARGS+=("${1}")
            fi
            shift
            ;;
    esac
done

# Target is required for run.
if [[ -z "${TARGET}" ]]; then
    iree_error "Target is required for bazel run"
    echo ""
    show_help
    exit 1
fi

# Set up worktree (after arg parsing so --help works anywhere).
iree_setup_worktree

if [[ "${TRACE_MODE}" == "1" && "${WATCH_MODE}" == "1" ]]; then
    iree_error "--trace is not supported with watch mode"
    exit 1
fi

if [[ -n "${TRACE_NAME}" && "${TRACE_MODE}" != "1" ]]; then
    iree_error "--trace_name requires --trace"
    exit 1
fi

# Prepend default configs to bazel args.
iree_bazel_build_default_configs
if [[ "${TRACE_MODE}" == "1" ]]; then
    BAZEL_ARGS=("${IREE_BAZEL_DEFAULT_CONFIGS[@]}" --config=perf --config=tracy "${BAZEL_ARGS[@]}")
else
    BAZEL_ARGS=("${IREE_BAZEL_DEFAULT_CONFIGS[@]}" "${BAZEL_ARGS[@]}")
fi

# Dry run: show what would be run and exit.
if iree_is_verbose || iree_is_dry_run; then
    if [[ "${WATCH_MODE}" == "1" ]]; then
        BAZEL_BIN=$(iree_get_bazel_command "${WATCH_MODE}")
        if [[ ${#PROGRAM_ARGS[@]} -gt 0 ]]; then
            iree_info "Command: ${BAZEL_BIN} run --run_under=\"cd ${IREE_BAZEL_ORIG_CWD} && \" ${BAZEL_ARGS[*]} ${TARGET} -- ${PROGRAM_ARGS[*]}"
        else
            iree_info "Command: ${BAZEL_BIN} run --run_under=\"cd ${IREE_BAZEL_ORIG_CWD} && \" ${BAZEL_ARGS[*]} ${TARGET}"
        fi
    else
        if [[ "${TRACE_MODE}" == "1" ]]; then
            TRACE_CAPTURE_NAME="${TRACE_NAME:-${TARGET}}"
            iree_info "Command: bazel build ${TARGET} ${BAZEL_ARGS[*]} && iree_tracy_capture.py --output-dir ${IREE_BAZEL_WORKTREE_DIR}/.tracy --name ${TRACE_CAPTURE_NAME} -- <binary> ${PROGRAM_ARGS[*]}"
        elif [[ ${#PROGRAM_ARGS[@]} -gt 0 ]]; then
            iree_info "Command: bazel build ${TARGET} ${BAZEL_ARGS[*]} && <binary> ${PROGRAM_ARGS[*]}"
        else
            iree_info "Command: bazel build ${TARGET} ${BAZEL_ARGS[*]} && <binary>"
        fi
    fi
    iree_info "Run directory: ${IREE_BAZEL_ORIG_CWD}"
fi

if iree_is_dry_run; then
    exit 0
fi

iree_debug "Running ${TARGET}"

if [[ "${WATCH_MODE}" == "1" ]]; then
    # Watch mode: use ibazel run with --run_under to match our cwd behavior.
    # NOTE: This holds the Bazel server lock while the binary runs, but that's
    # acceptable for a dedicated watch shell. ibazel needs to control the process
    # lifecycle to restart it on changes.
    BAZEL_BIN=$(iree_get_bazel_command "${WATCH_MODE}")
    CMD=("${BAZEL_BIN}" run --run_under="cd ${IREE_BAZEL_ORIG_CWD} && " "${BAZEL_ARGS[@]}" "${TARGET}")
    if [[ ${#PROGRAM_ARGS[@]} -gt 0 ]]; then
        CMD+=(-- "${PROGRAM_ARGS[@]}")
    fi
    iree_info "Watch mode: running from ${IREE_BAZEL_ORIG_CWD}"
    exec "${CMD[@]}"
else
    # Normal mode: build and execute from original working directory.
    # This releases the Bazel server lock before execution, allowing parallel builds.
    if [[ "${TRACE_MODE}" == "1" ]]; then
        iree_bazel_build_quiet "${TARGET}" "${BAZEL_ARGS[@]}" || exit $?
        BINARY_PATH=$(iree_bazel_get_binary_path "${TARGET}" "${BAZEL_ARGS[@]}")
        if [[ -z "${BINARY_PATH}" ]] || [[ ! -x "${BINARY_PATH}" ]]; then
            iree_error "Could not find built binary for ${TARGET}"
            exit 1
        fi

        cd "${IREE_BAZEL_ORIG_CWD}"
        TRACE_CAPTURE_NAME="${TRACE_NAME:-${TARGET}}"
        exec "${IREE_BAZEL_WORKTREE_DIR}/build_tools/tracing/iree_tracy_capture.py" \
            --output-dir "${IREE_BAZEL_WORKTREE_DIR}/.tracy" \
            --name "${TRACE_CAPTURE_NAME}" \
            -- "${BINARY_PATH}" "${PROGRAM_ARGS[@]}"
    else
        iree_bazel_build_and_exec "${TARGET}" "${IREE_BAZEL_ORIG_CWD}" "${BAZEL_ARGS[@]}" -- "${PROGRAM_ARGS[@]}"
    fi
fi
