# iree-bazel-lib - Shared functions for iree-bazel-* tools
#
# This file is meant to be sourced, not executed directly.
# Usage in scripts:
#   SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
#   source "$SCRIPT_DIR/iree-bazel-lib"
#   iree_bazel_init "iree-bazel-build"

# Prevent double-sourcing.
[[ -n "${_IREE_BAZEL_LIB_LOADED:-}" ]] && return 0
_IREE_BAZEL_LIB_LOADED=1

# -----------------------------------------------------------------------------
# Colors
# -----------------------------------------------------------------------------
_IREE_BAZEL_COLOR_GREEN='\033[0;32m'
_IREE_BAZEL_COLOR_YELLOW='\033[0;33m'
_IREE_BAZEL_COLOR_RED='\033[0;31m'
_IREE_BAZEL_COLOR_CYAN='\033[0;36m'
_IREE_BAZEL_COLOR_NC='\033[0m'

# Tool name for logging (set via iree_bazel_init).
_IREE_BAZEL_TOOL_NAME=""

# Bazel startup args for info/cquery (e.g., --output_base).
# Tools can set via iree_bazel_set_startup_args.
declare -a IREE_BAZEL_STARTUP_ARGS=()
IREE_BAZEL_EXECROOT=""

# -----------------------------------------------------------------------------
# Logging functions
# -----------------------------------------------------------------------------

# Print info message with tool name prefix (to stderr, never pollutes stdout).
iree_info() {
    printf "${_IREE_BAZEL_COLOR_GREEN}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
}

# Print warning message with tool name prefix (to stderr).
iree_warn() {
    printf "${_IREE_BAZEL_COLOR_YELLOW}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
}

# Print error message with tool name prefix to stderr.
iree_error() {
    printf "${_IREE_BAZEL_COLOR_RED}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
}

# Print debug message (only if IREE_BAZEL_VERBOSE is set).
iree_debug() {
    [[ "${IREE_BAZEL_VERBOSE:-0}" == "1" ]] && \
        printf "${_IREE_BAZEL_COLOR_CYAN}[${_IREE_BAZEL_TOOL_NAME}]${_IREE_BAZEL_COLOR_NC} %s\n" "${1}" >&2
    return 0
}

# -----------------------------------------------------------------------------
# Agent documentation
# -----------------------------------------------------------------------------

# Print a concise snippet for CLAUDE.md/AGENT.md files describing the bazel suite.
# Called by all iree-bazel-* tools via --agent-md flag.
iree_show_agent_md() {
    cat << 'EOF'
## iree-bazel-* Tools

Use `iree-bazel-*` wrappers instead of raw `bazel` commands. They handle
configuration, provide better defaults, and work from any subdirectory.

**Setup (ask user before first use):**
- Run `iree-bazel-configure` once per worktree to set up Bazel configuration
- Tools will error with helpful message if not configured yet

**Main tools:**
- `iree-bazel-build <target> [bazel-args]` - Build targets
- `iree-bazel-test <target> [bazel-args]` - Run tests
- `iree-bazel-run <target> [bazel-args] [-- program-args]` - Build and run binary
- `iree-bazel-try [options] -e 'code' [-- program-args]` - Quick C/C++ experiments

**Additional tools:** `iree-bazel-query`, `iree-bazel-cquery`, `iree-bazel-fuzz` (see `--help`)

**Examples:**
```shell
iree-bazel-build //tools:iree-compile
iree-bazel-test //runtime/src/iree/base:status_test
iree-bazel-run //tools:iree-compile -- --help
iree-bazel-try -e 'int main() { return 0; }'
```

**Output directories** (at repo root):
- `bazel-bin/` - built executables and libraries
- `bazel-testlogs/` - test outputs and logs

**Important:** Use absolute labels (`//tools:target` not `:target`). Try to stay
in the worktree directory when running commands.

Run `<tool> --help` for full documentation.
EOF
}

# -----------------------------------------------------------------------------
# Worktree detection
# -----------------------------------------------------------------------------

# Find the IREE worktree root (directory with .git and .bazelversion).
# Outputs the path to stdout, returns 1 if not found.
iree_find_worktree_root() {
    local directory="${1:-${PWD}}"
    while [[ "${directory}" != "/" ]]; do
        if [[ -e "${directory}/.git" ]] && [[ -f "${directory}/.bazelversion" ]]; then
            echo "${directory}"
            return 0
        fi
        directory=$(dirname "${directory}")
    done
    return 1
}

# Require being in a worktree, exit with error if not.
# Sets IREE_BAZEL_WORKTREE_DIR on success.
iree_require_worktree() {
    IREE_BAZEL_WORKTREE_DIR=$(iree_find_worktree_root)
    if [[ -z "${IREE_BAZEL_WORKTREE_DIR}" ]]; then
        iree_error "Not in an IREE worktree (no .bazelversion found)"
        iree_error "Must be in a directory with .git and .bazelversion files"
        exit 1
    fi
}

# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------

# Check that bazel is configured, error if not.
# Must be called after cd to worktree root.
iree_ensure_configured() {
    if [[ ! -f "configured.bazelrc" ]]; then
        iree_error "Bazel is not configured for this worktree"
        echo ""
        echo "Run this command to configure:"
        echo "  iree-bazel-configure"
        echo ""
        echo "For configuration options, run:"
        echo "  iree-bazel-configure --help"
        exit 1
    fi
}

# -----------------------------------------------------------------------------
# Initialization
# -----------------------------------------------------------------------------

# Initialize the library for a specific tool.
# Args:
#   $1 - Tool name (e.g., "iree-bazel-build")
#
# This sets up:
#   - Tool name for logging
#   - IREE_BAZEL_ORIG_CWD (original working directory before any cd)
#   - IREE_BAZEL_DRY_RUN (default 0)
#   - IREE_BAZEL_VERBOSE (default 0)
iree_bazel_init() {
    _IREE_BAZEL_TOOL_NAME="${1}"
    IREE_BAZEL_ORIG_CWD="${PWD}"
    IREE_BAZEL_DRY_RUN="${IREE_BAZEL_DRY_RUN:-0}"
    IREE_BAZEL_VERBOSE="${IREE_BAZEL_VERBOSE:-0}"
}

# Set startup args for bazel info/cquery and clear cached execroot.
iree_bazel_set_startup_args() {
    IREE_BAZEL_STARTUP_ARGS=("$@")
    IREE_BAZEL_EXECROOT=""
}

# Set up the worktree environment.
# Combines: require worktree + cd to it + ensure configured.
# Must be called after parsing args (so --help can work outside worktree).
iree_setup_worktree() {
    iree_require_worktree
    cd "${IREE_BAZEL_WORKTREE_DIR}"
    iree_ensure_configured
}

# Check if dry-run mode is enabled.
iree_is_dry_run() {
    [[ "${IREE_BAZEL_DRY_RUN}" == "1" ]]
}

# Check if verbose mode is enabled.
iree_is_verbose() {
    [[ "${IREE_BAZEL_VERBOSE}" == "1" ]]
}

# Get the bazel command to use (bazel or ibazel for watch mode).
# Assumes bazelisk is installed as 'bazel' (standard installation pattern).
iree_get_bazel_command() {
    local watch_mode="${1:-0}"

    if [[ "${watch_mode}" == "1" ]]; then
        if ! command -v ibazel >/dev/null 2>&1; then
            iree_error "ibazel not found. Install with: go install github.com/bazelbuild/bazel-watcher/cmd/ibazel@latest"
            exit 1
        fi
        echo "ibazel"
    else
        echo "bazel"
    fi
}

# -----------------------------------------------------------------------------
# Argument preprocessing
# -----------------------------------------------------------------------------

# Expand combined short flags (e.g., -nv -> -n -v).
# This allows Unix-style combined flags as documented in help text.
#
# Usage: Call before argument parsing loop to expand combined flags.
#   eval "set -- $(iree_expand_combined_flags "$@")"
#
# Examples:
#   -nv   -> -n -v
#   -nvk  -> -n -v -k
#   --foo -> --foo (unchanged)
#   -n    -> -n (unchanged)
iree_expand_combined_flags() {
    local result=()
    while [[ $# -gt 0 ]]; do
        local arg="${1}"
        shift
        # Match single dash followed by 2+ lowercase letters (no '=' or other chars).
        if [[ "${arg}" =~ ^-([a-z]{2,})$ ]]; then
            local flags="${BASH_REMATCH[1]}"
            for (( i=0; i<${#flags}; i++ )); do
                result+=("-${flags:${i}:1}")
            done
        else
            result+=("${arg}")
        fi
    done
    # Print quoted args suitable for eval.
    printf '%q ' "${result[@]}"
}

# -----------------------------------------------------------------------------
# Default configuration flags
# -----------------------------------------------------------------------------

# Build the list of default bazel config flags.
# These are inserted before user-provided flags so users can override.
# Populates IREE_BAZEL_DEFAULT_CONFIGS array.
#
# Current defaults:
#   --config=localdev    Local dev optimizations (disk cache, skymeld)
#   --verbose_failures   (only in verbose mode) Show full command on failure
#
# Future: auto-detect hardware and enable drivers:
#   - HIP_PATH -> --config=hip
#   - VULKAN_SDK -> --config=vulkan
#   - CUDA_PATH -> --config=cuda
iree_bazel_build_default_configs() {
    IREE_BAZEL_DEFAULT_CONFIGS=()

    # Always use localdev for interactive development.
    IREE_BAZEL_DEFAULT_CONFIGS+=(--config=localdev)

    # Verbose mode: show full commands on failure.
    if iree_is_verbose; then
        IREE_BAZEL_DEFAULT_CONFIGS+=(--verbose_failures)
    fi

    # Use mold linker if available (significantly faster than ld/gold).
    # TODO(benvanik): this breaks --features=thin_lto, so leaving it opt-in for
    #                 now (--config=mold) until I can find a good way to choose.
    # if command -v mold &>/dev/null; then
    #     IREE_BAZEL_DEFAULT_CONFIGS+=(--config=mold)
    #     iree_debug "Using mold linker"
    # fi

    # TODO: Auto-detect available hardware/SDKs.
    # if [[ -n "${HIP_PATH:-}" ]]; then
    #     IREE_BAZEL_DEFAULT_CONFIGS+=(--config=hip)
    # fi
    # if [[ -n "${VULKAN_SDK:-}" ]]; then
    #     IREE_BAZEL_DEFAULT_CONFIGS+=(--config=vulkan)
    # fi

    iree_debug "Default configs: ${IREE_BAZEL_DEFAULT_CONFIGS[*]}"
}

# -----------------------------------------------------------------------------
# Build and execution
# -----------------------------------------------------------------------------

# Get execroot for the current Bazel server (cached).
# Must be called from the worktree root directory.
iree_bazel_get_execroot() {
    if [[ -n "${IREE_BAZEL_EXECROOT}" ]]; then
        echo "${IREE_BAZEL_EXECROOT}"
        return 0
    fi
    local BAZEL_BIN
    BAZEL_BIN=$(iree_get_bazel_command)
    local info_output execroot
    info_output=$("${BAZEL_BIN}" "${IREE_BAZEL_STARTUP_ARGS[@]}" info execution_root 2>/dev/null || true)
    execroot=$(echo "${info_output}" | sed -n 's/^execution_root: //p')
    if [[ -z "${execroot}" ]]; then
        execroot="${info_output}"
    fi
    if [[ -n "${execroot}" ]]; then
        IREE_BAZEL_EXECROOT="${execroot}"
        echo "${IREE_BAZEL_EXECROOT}"
    fi
}

# Get the output file path for a bazel target.
# Must be called from the worktree root directory.
# Args:
#   $1 - Bazel target label
#   $2... - Bazel args (configs, etc.) - MUST match the build args
# Outputs: Absolute path to built binary
iree_bazel_get_binary_path() {
    local target="${1}"
    shift
    local BAZEL_BIN
    BAZEL_BIN=$(iree_get_bazel_command)
    local relative_paths relative_path execroot
    # Pass all bazel args to cquery so it uses the same configuration as build.
    relative_paths=$("${BAZEL_BIN}" "${IREE_BAZEL_STARTUP_ARGS[@]}" cquery --output=files "${target}" "$@" 2>/dev/null)
    relative_path="${relative_paths%%$'\n'*}"
    if [[ -n "${relative_path}" ]]; then
        execroot=$(iree_bazel_get_execroot)
        if [[ -n "${execroot}" ]]; then
            echo "${execroot}/${relative_path}"
        else
            # Fallback to workspace-relative path if execroot is unavailable.
            echo "${PWD}/${relative_path}"
        fi
    fi
}

# Build a bazel target and execute it from the original working directory.
# This solves the problem where bazel run changes cwd to the execroot.
#
# Args:
#   $1 - Bazel target label
#   $2 - Original working directory to run from
#   $3... - Bazel args (everything before --)
#   After finding "--" in args, remaining args go to the program
#
# Example:
#   iree_bazel_build_and_exec "//tools:iree-compile" "${IREE_BAZEL_ORIG_CWD}" \
#       --config=debug -- input.mlir -o output.vmfb
iree_bazel_build_and_exec() {
    local target="${1}"
    local run_cwd="${2}"
    shift 2

    local -a bazel_args=()
    local -a program_args=()
    local parsing_program_args=0

    # Split args into bazel args and program args.
    while [[ $# -gt 0 ]]; do
        if [[ "${parsing_program_args}" == "1" ]]; then
            program_args+=("${1}")
        elif [[ "${1}" == "--" ]]; then
            parsing_program_args=1
        else
            bazel_args+=("${1}")
        fi
        shift
    done

    iree_debug "Bazel args: ${bazel_args[*]:-<none>}"
    iree_debug "Program args: ${program_args[*]:-<none>}"

    # Build the target quietly (no output on success).
    if ! iree_bazel_build_quiet "${target}" "${bazel_args[@]}"; then
        return $?
    fi

    # Get the binary path (pass same args so cquery uses same configuration).
    local binary_path
    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}"
        return 1
    fi

    iree_debug "Binary: ${binary_path}"
    iree_debug "Running from: ${run_cwd}"

    # Run from original directory.
    cd "${run_cwd}"
    exec "${binary_path}" "${program_args[@]}"
}

# Build a bazel target only (no execution).
# Returns the binary path via stdout.
#
# Args:
#   $1 - Bazel target label
#   $2... - Bazel args
#
# Example:
#   binary_path=$(iree_bazel_build_only "//tools:iree-compile" --config=debug)
iree_bazel_build_only() {
    local target="${1}"
    shift
    local BAZEL_BIN
    BAZEL_BIN=$(iree_get_bazel_command)
    local -a bazel_args=("$@")

    iree_debug "Building ${target}..."

    if ! "${BAZEL_BIN}" build "${target}" "${bazel_args[@]}"; then
        return $?
    fi

    # Pass same args so cquery uses same configuration.
    iree_bazel_get_binary_path "${target}" "${bazel_args[@]}"
}

# Build a bazel target quietly - no output on success, full output on failure.
# Use this for run/try tools where we want clean output by default.
#
# Args:
#   $1 - Bazel target label
#   $2... - Bazel args
#
# Returns: exit code from bazel build
iree_bazel_build_quiet() {
    local target="${1}"
    shift
    local BAZEL_BIN
    BAZEL_BIN=$(iree_get_bazel_command)

    iree_debug "Building ${target}..."

    if iree_is_verbose; then
        # Verbose mode: show everything.
        "${BAZEL_BIN}" build "${target}" "$@"
        return $?
    fi

    # Quiet mode: capture output, only show on failure.
    # Force colors since we'll display to terminal on failure.
    local log_file
    log_file=$(mktemp)
    trap "rm -f '${log_file}'" RETURN

    if "${BAZEL_BIN}" build --color=yes "${target}" "$@" >"${log_file}" 2>&1; then
        # Check for warnings in log and report count.
        local warning_count
        warning_count=$(grep -c -E '(warning:|WARNING:)' "${log_file}" 2>/dev/null || echo 0)
        if [[ "${warning_count}" -gt 0 ]]; then
            iree_warn "Build succeeded with ${warning_count} warning(s) (use -v to see)"
        fi
        return 0
    else
        local exit_code=$?
        # Build failed - show the captured output.
        cat "${log_file}" >&2
        return ${exit_code}
    fi
}
