#!/usr/bin/env bash

import "$SHELLOPS_DIR/lib/cargo/api.sh"

: <<'DOC'
Rust project automation API.

Provides rustup setup helpers plus common Cargo checks used by CI jobs and
repository scripts.
DOC

# ── Toolchain setup ──────────────────────────────────────────────────

function rust_setup() {
  local toolchain="${1:-stable}"
  shift || true

  local components=("$@")

  __rust__ensure_path
  __rust__ensure_rustup "$toolchain" "${components[@]}"

  rustup default "$toolchain"
  rustup show
  log "rust $(rustc --version) ready"
}

function __rust__ensure_path() {
  if [[ -n "${GITHUB_PATH:-}" ]]; then
    echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
  fi

  export PATH="$HOME/.cargo/bin:$PATH"
}

function rust_source_cargo_env() {
  [[ -f "$HOME/.cargo/env" ]] || return 0
  # shellcheck disable=SC1091
  source "$HOME/.cargo/env"
}

function rust_install_rustup() {
  local version="${1:-}"
  shift || true

  local installer
  installer="$(curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs)"

  if [[ -n "$version" ]]; then
    RUSTUP_VERSION="$version" sh -c "$installer" -- "$@"
  else
    sh -c "$installer" -- "$@"
  fi

  rust_source_cargo_env
  __rust__ensure_path
}

function rust_uninstall_rustup() {
  rust_source_cargo_env

  if command -v rustup >/dev/null 2>&1; then
    rustup self uninstall -y
  fi
}

function __rust__ensure_rustup() {
  local toolchain="$1"
  shift

  if command -v rustup &>/dev/null; then
    __rust__install_toolchain "$toolchain" "$@"
    return
  fi

  __rust__install_rustup_toolchain "$toolchain" "$@"
}

function __rust__install_rustup_toolchain() {
  local toolchain="$1"
  shift

  local args=(-y --default-toolchain "$toolchain" --profile minimal)
  local component

  for component in "$@"; do
    args+=(-c "$component")
  done

  rust_install_rustup "" "${args[@]}"
}

function __rust__install_toolchain() {
  local toolchain="$1"
  shift

  local component

  rustup toolchain install "$toolchain" --profile minimal

  for component in "$@"; do
    rustup component add "$component" --toolchain "$toolchain"
  done
}

# ── Cargo operations ─────────────────────────────────────────────────

function rust_fmt_check() {
  cargo fmt --all -- --check
}

function rust_clippy_workspace() {
  cargo clippy --workspace --all-targets --no-default-features -- -D warnings
}

function rust_test_workspace() {
  cargo test --workspace --no-default-features
}

function rust_coverage_workspace() {
  local threshold="${1:-90}"

  rust_cargo_install_locked cargo-tarpaulin
  cargo tarpaulin --engine llvm --workspace --no-default-features --branch --fail-under "$threshold" --out xml
}

function rust_fuzz_check() {
  local project_dir="${1:?usage: rust_fuzz_check <project-dir> <target>}"
  local target="${2:?usage: rust_fuzz_check <project-dir> <target>}"

  rust_install_cargo_fuzz
  (
    cd "$project_dir" || exit
    cargo +nightly fuzz check "$target"
  )
}

function rust_fuzz_replay() {
  local project_dir="${1:?usage: rust_fuzz_replay <project-dir> <target>}"
  local target="${2:?usage: rust_fuzz_replay <project-dir> <target>}"

  rust_install_cargo_fuzz
  (
    cd "$project_dir" || exit
    cargo +nightly fuzz run "$target" -- -runs=0
  )
}

function rust_fuzz_explore() {
  local project_dir="${1:?usage: rust_fuzz_explore <project-dir> <target> <seconds>}"
  local target="${2:?usage: rust_fuzz_explore <project-dir> <target> <seconds>}"
  local seconds="${3:?usage: rust_fuzz_explore <project-dir> <target> <seconds>}"

  rust_fuzz_validate_positive_integer "$seconds" "seconds"
  rust_install_cargo_fuzz
  (
    cd "$project_dir" || exit
    mkdir -p "fuzz/artifacts/$target" "fuzz/corpus/$target"
    cargo +nightly fuzz run "$target" -- -max_total_time="$seconds"
  )
}

function rust_fuzz_explore_minutes() {
  local project_dir="${1:?usage: rust_fuzz_explore_minutes <project-dir> <target> <minutes>}"
  local target="${2:?usage: rust_fuzz_explore_minutes <project-dir> <target> <minutes>}"
  local minutes="${3:?usage: rust_fuzz_explore_minutes <project-dir> <target> <minutes>}"

  rust_fuzz_validate_positive_integer "$minutes" "minutes"
  rust_fuzz_explore "$project_dir" "$target" "$((minutes * 60))"
}

function rust_fuzz_explore_minutes_nonblocking() {
  local project_dir="${1:?usage: rust_fuzz_explore_minutes_nonblocking <project-dir> <target> <minutes>}"
  local target="${2:?usage: rust_fuzz_explore_minutes_nonblocking <project-dir> <target> <minutes>}"
  local minutes="${3:?usage: rust_fuzz_explore_minutes_nonblocking <project-dir> <target> <minutes>}"

  rust_fuzz_validate_positive_integer "$minutes" "minutes"
  if ! rust_fuzz_explore_minutes "$project_dir" "$target" "$minutes"; then
    log "Fuzz exploration exited non-zero; continuing so crash submission can collect artifacts."
  fi
}

function rust_fuzz_validate_positive_integer() {
  local value="${1:?usage: rust_fuzz_validate_positive_integer <value> <label>}"
  local label="${2:?usage: rust_fuzz_validate_positive_integer <value> <label>}"

  if [[ ! "$value" =~ ^[0-9]+$ || "$value" -eq 0 ]]; then
    error "Fuzz $label must be a positive integer: $value"
    exit 1
  fi
}

function rust_install_cargo_fuzz() {
  rust_cargo_install_locked cargo-fuzz
}

function rust_audit() {
  rust_cargo_install_locked cargo-audit
  cargo audit
}

function rust_cargo_install_locked() {
  local crate="${1:?usage: rust_cargo_install_locked <crate>}"
  local cargo_home="${CARGO_HOME:-$HOME/.cargo}"

  if command -v "$crate" >/dev/null 2>&1; then
    return 0
  fi

  if [[ -x "$cargo_home/bin/$crate" ]]; then
    return 0
  fi

  cargo install "$crate" --locked
}

: <<'DOC'
Returns the platform-appropriate shared library filename for a given name.

  rust_shared_lib_filename godot
  # => libgodot.so  (Linux)
  # => libgodot.dylib (macOS)
DOC
function rust_shared_lib_filename() {
  local name="$1"
  case "$(uname -s)" in
    Darwin) echo "lib${name}.dylib" ;;
    *)      echo "lib${name}.so" ;;
  esac
}


