#!/usr/bin/env bash

import "$SHELLOPS_DIR/lib/xplat/api.sh"
import "$SHELLOPS_DIR/lib/rust/api.sh"
import "$SHELLOPS_DIR/lib/http.api.sh"

: <<'DOC'
Cross-compilation API built on Zig and cargo-zigbuild.

Provides Zig toolchain installation, cargo-zigbuild invocation for
individual or batched target triples, and cross-arch binary stripping
via zig objcopy.
DOC

ZIGBUILD_DEFAULT_ZIG_VERSION="0.16.0"
ZIGBUILD_ZIG_BASE_URL="https://ziglang.org/download"

: <<'DOC'
Installs both Zig and cargo-zigbuild.

  zigbuild_install
  zigbuild_install "0.16.0"
DOC
function zigbuild_install() {
  local zig_version="${1:-$ZIGBUILD_DEFAULT_ZIG_VERSION}"
  zigbuild_install_zig "$zig_version"
  zigbuild_install_cargo_zigbuild
}

: <<'DOC'
Downloads and extracts a Zig binary release for the host platform.
Sets ZIG_DIR in the environment and adds it to PATH.

  zigbuild_install_zig
  zigbuild_install_zig "0.16.0"
DOC
function zigbuild_install_zig() {
  local version="${1:-$ZIGBUILD_DEFAULT_ZIG_VERSION}"

  if __zigbuild__zig_already_installed "$version"; then
    log "zig $version already installed"
    __zigbuild__export_zig_env "$version"
    return
  fi

  local url filename
  filename="$(__zigbuild__zig_tarball_name "$version")"
  url="${ZIGBUILD_ZIG_BASE_URL}/${version}/${filename}"

  log "downloading zig ${version} from ${url}"
  __zigbuild__download_and_extract "$url" "$filename" "$version"
  __zigbuild__export_zig_env "$version"
  log "zig $(zig version) ready"
}

function __zigbuild__zig_already_installed() {
  local version="$1"
  local install_dir
  install_dir="$(__zigbuild__zig_install_dir "$version")"
  [[ -x "${install_dir}/zig" ]]
}

function __zigbuild__zig_install_dir() {
  local version="$1"
  local zig_os zig_arch
  zig_os="$(__zigbuild__zig_os)"
  zig_arch="$(xplat_arch_uname)"
  echo "${ZIGBUILD_ZIG_HOME:-${HOME}/.zig}/zig-${zig_arch}-${zig_os}-${version}"
}

function __zigbuild__zig_tarball_name() {
  local version="$1"
  local zig_os zig_arch
  zig_os="$(__zigbuild__zig_os)"
  zig_arch="$(xplat_arch_uname)"
  echo "zig-${zig_arch}-${zig_os}-${version}.tar.xz"
}

function __zigbuild__zig_os() {
  local os
  os="$(xplat_os)"
  case "$os" in
    darwin) echo "macos" ;;
    *)      echo "$os" ;;
  esac
}

function __zigbuild__download_and_extract() {
  local url="$1" filename="$2" version="$3"
  local zig_home="${ZIGBUILD_ZIG_HOME:-${HOME}/.zig}"
  local tmpfile

  mkdir -p "$zig_home"
  tmpfile="$(mktemp)"

  if ! http_download "$url" "$tmpfile"; then
    return 1
  fi

  tar -xJf "$tmpfile" -C "$zig_home"
  rm -f "$tmpfile"
}

function __zigbuild__export_zig_env() {
  local version="$1"
  local install_dir
  install_dir="$(__zigbuild__zig_install_dir "$version")"

  export ZIG_DIR="$install_dir"
  export PATH="${install_dir}:${PATH}"

  if [[ -n "${GITHUB_PATH:-}" ]]; then
    echo "$install_dir" >> "$GITHUB_PATH"
  fi
}

: <<'DOC'
Removes a Zig installation for the given version.

  zigbuild_uninstall_zig "0.16.0"
DOC
function zigbuild_uninstall_zig() {
  local version="${1:-$ZIGBUILD_DEFAULT_ZIG_VERSION}"
  local install_dir
  install_dir="$(__zigbuild__zig_install_dir "$version")"

  if [[ -d "$install_dir" ]]; then
    log "removing zig ${version}"
    rm -rf "$install_dir"
  else
    log "zig ${version} not installed, nothing to remove"
  fi
}

: <<'DOC'
Installs cargo-zigbuild via cargo install.

  zigbuild_install_cargo_zigbuild
DOC
function zigbuild_install_cargo_zigbuild() {
  rust_cargo_install_locked cargo-zigbuild
}

: <<'DOC'
Adds the Rust standard library stub for each target triple via rustup.

  zigbuild_add_targets '["x86_64-apple-darwin","aarch64-unknown-linux-gnu"]'
DOC
function zigbuild_add_targets() {
  local triples="$1"
  jq_foreach __zigbuild__add_one_target "$triples"
}

function __zigbuild__add_one_target() {
  local triple
  triple="$(jq_unwrap "$1")"
  log "adding rustup target: ${triple}"
  rustup target add "$triple"
}

: <<'DOC'
Builds a Rust project for a single target triple via cargo zigbuild.

  zigbuild_build "x86_64-apple-darwin"
  zigbuild_build "aarch64-unknown-linux-gnu" --no-default-features

Extra arguments are forwarded to cargo zigbuild.
DOC
function zigbuild_build() {
  local triple="$1"
  shift

  log "building for ${triple}"
  cargo zigbuild --release --target "$triple" "$@"
}

: <<'DOC'
Builds for every triple in a JSON array, forwarding extra cargo flags.

  zigbuild_build_all '["x86_64-apple-darwin","aarch64-unknown-linux-gnu"]'
  zigbuild_build_all '["x86_64-apple-darwin"]' --no-default-features
DOC
function zigbuild_build_all() {
  local triples="$1"
  shift

  zigbuild_add_targets "$triples"
  jq_foreach __zigbuild__build_one "$triples" "$@"
}

function __zigbuild__build_one() {
  local triple
  triple="$(jq_unwrap "$1")"
  shift
  zigbuild_build "$triple" "$@"
}

: <<'DOC'
Strips a binary of debug symbols. Detects Mach-O vs ELF and picks
the right tool: llvm-strip for Darwin, zig objcopy for ELF, with
fallbacks to each other and system strip.

  zigbuild_strip "target/x86_64-apple-darwin/release/mybin" "x86_64-apple-darwin"
DOC
function zigbuild_strip() {
  local binary="$1" triple="${2:-}"

  log "stripping ${binary}"

  if __zigbuild__is_darwin_triple "$triple"; then
    __zigbuild__strip_macho "$binary" || warn "failed to strip ${binary}, continuing"
  else
    __zigbuild__strip_elf "$binary" || warn "failed to strip ${binary}, continuing"
  fi
}

function __zigbuild__is_darwin_triple() {
  [[ "$1" == *-apple-darwin* ]]
}

function __zigbuild__strip_macho() {
  local binary="$1"

  if command -v llvm-strip >/dev/null 2>&1; then
    llvm-strip "$binary"
    return
  fi

  if [[ "$(xplat_os)" == "darwin" ]] && command -v strip >/dev/null 2>&1; then
    strip "$binary"
    return
  fi

  warn "no strip tool available for Mach-O binary, skipping"
}

function __zigbuild__strip_elf() {
  local binary="$1"

  if command -v llvm-strip >/dev/null 2>&1; then
    llvm-strip "$binary"
    return
  fi

  if command -v zig >/dev/null 2>&1; then
    zig objcopy -S "$binary" "${binary}.stripped" && mv "${binary}.stripped" "$binary"
    return
  fi

  strip "$binary"
}

: <<'DOC'
Strips debug symbols from a binary across all target triples.

  zigbuild_strip_all "mybin" '["x86_64-apple-darwin","aarch64-unknown-linux-gnu"]'
DOC
function zigbuild_strip_all() {
  local bin_name="$1" triples="$2"
  jq_foreach __zigbuild__strip_one "$triples" "$bin_name"
}

function __zigbuild__strip_one() {
  local triple bin_name="$2"
  triple="$(jq_unwrap "$1")"
  zigbuild_strip "target/${triple}/release/${bin_name}" "$triple"
}
